PAGI Full Demo

A comprehensive example demonstrating all major PAGI features in a single application.

Features

Running the Server

pagi-server --app examples/full-demo/app.pl --port 5000

Endpoints

| Endpoint | Method/Type | Description | |----------|-------------|-------------| | / | GET | Returns "Hello, World!" | | /echo | POST | Echoes back the request body | | /stream | GET | Streams 5 chunks with 0.5s delays | | /ws/echo | WebSocket | Echoes text and binary frames | | /events | SSE | Sends 10 tick events, 1 per second |

Testing

Hello World

curl http://localhost:5000/
# Hello, World!

POST Echo

curl -X POST -d "Hello PAGI" http://localhost:5000/echo
# Hello PAGI

curl -X POST -H "Content-Type: application/json" \
     -d '{"message":"test"}' http://localhost:5000/echo
# {"message":"test"}

HTTP Streaming

curl http://localhost:5000/stream
# Stream started (request #0)
# Chunk 1: Processing...
# Chunk 2: Working...
# Chunk 3: Almost done...
# Stream complete!

Server-Sent Events

SSE requires the Accept: text/event-stream header:

curl -N -H "Accept: text/event-stream" http://localhost:5000/events
# event: tick
# id: 1
# data: Event #1 at 1704384000
#
# event: tick
# id: 2
# data: Event #2 at 1704384001
# ...
# event: done
# data: Stream complete

WebSocket

Using websocat:

websocat ws://localhost:5000/ws/echo
> Hello
< Echo: Hello
> Test message
< Echo: Test message

Using JavaScript:

const ws = new WebSocket('ws://localhost:5000/ws/echo');
ws.onmessage = (e) => console.log('Received:', e.data);
ws.onopen = () => ws.send('Hello from browser!');

Code Structure

# Lifespan handling with PAGI::Utils::handle_lifespan
return await handle_lifespan($scope, $receive, $send,
    startup  => async sub { ... },
    shutdown => async sub { ... },
) if $scope->{type} eq 'lifespan';

# Routing with PAGI::App::Router
my $router = PAGI::App::Router->new;
$router->get('/' => async sub { ... })->name('hello');
$router->post('/echo' => async sub { ... })->name('echo');
$router->websocket('/ws/echo' => async sub { ... });
$router->sse('/events' => async sub { ... });

Lifespan State

The lifespan startup hook initializes shared state accessible to all requests:

startup => async sub {
    my ($state) = @_;
    $state->{request_counter} = 0;
    $state->{started_at} = time();
    # Initialize DB connections, caches, etc. here
}

Note: Avoid using Future::IO->sleep in lifespan hooks as the event loop may not be fully initialized. Use synchronous initialization or the maybe_sleep helper pattern shown in the example.

Access in handlers via $scope->{state}:

my $counter = $scope->{state}{request_counter}++;

See Also