PAGI-Tools

 view release on metacpan or  search on metacpan

lib/PAGI/Tools/Tutorial.pod  view on Meta::CPAN

=encoding UTF-8

=head1 NAME

PAGI::Tools::Tutorial - Optional convenience helpers for PAGI applications

=head1 DESCRIPTION

PAGI itself is a protocol; see L<PAGI::Tutorial> for the protocol tutorial.
The C<PAGI-Tools> distribution adds B<optional> convenience helpers built on
top of that protocol: middleware, request/response sugar, WebSocket and SSE
helpers, routers, and ready-made applications. None of these are required to
write a complete PAGI application; they exist to save you boilerplate when you
want it, and every one of them is an ordinary PAGI application or a plain
wrapper around the C<$scope>/C<$receive>/C<$send> protocol. This guide covers
them.

=head1 PART 1: HELLO WORLD

A PAGI application is an C<async> sub that receives three arguments: C<$scope>
(the request metadata), C<$receive> (pull request-body events), and C<$send>
(push response events). At the raw protocol level, "hello world" emits two
events — the response start (status and headers) and the body:

  use Future::AsyncAwait;

  my $app = async sub {
      my ($scope, $receive, $send) = @_;
      await $send->({
          type    => 'http.response.start',
          status  => 200,
          headers => [['content-type', 'text/plain']],
      });
      await $send->({
          type => 'http.response.body',
          body => 'Hello, world!',
      });
  };

That is a complete, server-ready application — no toolkit required. The helpers
in this distribution simply make the common cases shorter. Here is the same
response built as a L<PAGI::Response> value:

  use Future::AsyncAwait;
  use PAGI::Response;

  my $app = async sub {
      my ($scope, $receive, $send) = @_;
      await PAGI::Response->text('Hello, world!')->respond($send);
  };

C<PAGI::Response> assembles the two events for you, and C<respond> sends them.
Returning JSON is just as short:

  await PAGI::Response->json({ hello => 'world' })->respond($send);

From here the tutorial builds up: routing requests to handlers (PART 2),
reading form and JSON input (PART 3), the full response builder (PART 4),
real-time and streaming (PART 5), middleware (PART 6), and composing larger
applications (PART 7).

=head1 PART 2: ROUTING

=head2 2.1 PAGI::App::Router - Basic Routing

L<PAGI::App::Router> provides lightweight functional routing:

  use PAGI::App::Router;
  use PAGI::Response;

  my $router = PAGI::App::Router->new;

  # Mount a static response value — dispatch sends it automatically
  $router->mount('/health' => PAGI::Response->json({ ok => \1 }));

  # HTTP routes
  $router->get('/' => async sub {
      my ($scope, $receive, $send) = @_;
      my $res = PAGI::Response->new($scope);
      await $res->text('Home')->respond($send);
  });

  $router->post('/users' => async sub {
      my ($scope, $receive, $send) = @_;
      my $res = PAGI::Response->new($scope);
      await $res->json({ created => 1 }, status => 201)->respond($send);
  });

  # Path parameters
  $router->get('/users/:id' => async sub {
      my ($scope, $receive, $send) = @_;
      my $req = PAGI::Request->new($scope, $receive);
      my $res = $req->response;
      my $id = $req->path_param('id');
      await $res->json({ id => $id })->respond($send);
  });

  # WebSocket and SSE handlers drive $send imperatively — no return value
  $router->websocket('/ws' => async sub { ... });
  $router->sse('/events' => async sub { ... });

  $router->to_app;

For advanced routing patterns (nested routers, route-level middleware, class-based routing), see L<PAGI::Tools::Cookbook>.

=head1 PART 3: HANDLING REQUESTS

Once a request is routed to a handler, L<PAGI::Request> turns the raw C<$scope>
and C<$receive> into a convenient object: query and path parameters, headers,
cookies, and — the focus of this part — form bodies, JSON bodies, and file
uploads. It handles UTF-8 decoding and body parsing for you.

=head2 3.1 PAGI::Request - Request Parsing

L<PAGI::Request> parses HTTP requests and provides convenient accessors for headers, query parameters, cookies, and request bodies.

=head3 Creating a Request

  my $req = PAGI::Request->new($scope, $receive);

lib/PAGI/Tools/Tutorial.pod  view on Meta::CPAN

          await $app->($scope, $receive, $send);
      };
  }

=item * Intercept responses

Modify outgoing events:

  sub wrap {
      my ($self, $app) = @_;
      return async sub {
          my ($scope, $receive, $send) = @_;

          my $wrapped_send = $self->intercept_send($send, async sub {
              my ($event, $original_send) = @_;
              if ($event->{type} eq 'http.response.start') {
                  push @{$event->{headers}}, ['x-custom', 'value'];
              }
              await $original_send->($event);
          });

          await $app->($scope, $receive, $wrapped_send);
      };
  }

=item * Short-circuit requests

Return early without calling inner app:

  sub wrap {
      my ($self, $app) = @_;
      return async sub {
          my ($scope, $receive, $send) = @_;

          # Check condition
          unless ($self->is_authorized($scope)) {
              await $send->({
                  type    => 'http.response.start',
                  status  => 403,
                  headers => [['content-type', 'text/plain']],
              });
              await $send->({
                  type => 'http.response.body',
                  body => 'Forbidden',
              });
              return;  # Don't call $app
          }

          await $app->($scope, $receive, $send);
      };
  }

=back

=head1 PART 7: COMPOSING APPLICATIONS

PAGI ships with several ready-to-use applications for common tasks. These can be mounted with routers or used standalone.

=head2 7.1 PAGI::App::File - Static Files

L<PAGI::App::File> serves static files with security, caching, and streaming:

  use PAGI::App::File;

  my $static = PAGI::App::File->new(root => './public');
  $router->mount('/static' => $static);

Features:

=over 4

=item * Efficient streaming (large files don't consume memory)

=item * ETag caching with 304 Not Modified support

=item * HTTP Range requests for resume support

=item * Automatic MIME type detection

=item * Security: path traversal protection

=back

=head2 7.2 PAGI::App::Healthcheck - Health Endpoints

L<PAGI::App::Healthcheck> creates health check endpoints:

  use PAGI::App::Healthcheck;

  my $health = PAGI::App::Healthcheck->new(
      version => '1.0.0',
      checks => {
          database => sub { $db && $db->ping },
          cache    => sub { $redis && $redis->ping },
      },
  );

  $router->mount('/health' => $health);

=head2 7.3 PAGI::App::URLMap - Mount Applications

L<PAGI::App::URLMap> routes requests to different apps based on URL prefix:

  use PAGI::App::URLMap;

  my $urlmap = PAGI::App::URLMap->new;
  $urlmap->mount('/api' => $api_app);
  $urlmap->mount('/admin' => $admin_app);
  $urlmap->mount('/static' => $static);
  $urlmap->to_app;

=head2 7.4 PAGI::App::Cascade - Try Apps in Sequence

L<PAGI::App::Cascade> tries apps in order until one returns a non-404:

  use PAGI::App::Cascade;

  my $app = PAGI::App::Cascade->new(
      apps => [$static, $api, $fallback],
      catch => [404, 405],
  );

=head2 7.5 PAGI::App::Proxy - Reverse Proxy

L<PAGI::App::Proxy> forwards requests to backend servers:

  use PAGI::App::Proxy;

  my $proxy = PAGI::App::Proxy->new(
      backend => 'http://localhost:8080',
      timeout => 30,
  );



( run in 0.817 second using v1.01-cache-2.11-cpan-6aa56a78535 )