DEV Community

John Napiorkowski
John Napiorkowski

Posted on

PAGI: ASGI For Perl, or the Spiritual Successor to Plack

Introducing PAGI: Async Web Development for Perl

TL;DR: PAGI (Perl Asynchronous Gateway Interface) is a new specification for async Perl web applications, inspired by Python's ASGI. It supports HTTP, WebSockets, and Server-Sent Events natively, and can wrap existing PSGI applications for backward compatibility.


The Problem

Modern web applications need more than traditional request-response cycles. Real-time features like live notifications, collaborative editing, and streaming data require persistent connections. This means:

  • WebSockets for bidirectional communication
  • Server-Sent Events for efficient server push
  • Streaming responses for large payloads
  • Connection lifecycle management for resource pooling

PSGI, Perl's venerable web server interface, assumes a synchronous world. While frameworks like Mojolicious have built async capabilities on top, there's no shared standard that allows different async frameworks and servers to interoperate.

PAGI aims to fill that gap.

What is PAGI?

PAGI defines a standard interface between async-capable Perl web servers and applications. If you're familiar with Python's ecosystem, think of it as Perl's answer to ASGI.

A PAGI application is an async coderef with three parameters:

use Future::AsyncAwait;
use experimental 'signatures';

async sub app ($scope, $receive, $send) {
    # $scope   - connection metadata (type, headers, path, etc.)
    # $receive - async coderef to get events from the client
    # $send    - async coderef to send events to the client
}
Enter fullscreen mode Exit fullscreen mode

The $scope->{type} tells you what kind of connection you're handling:

Type Description
http Standard HTTP request/response
websocket Persistent WebSocket connection
sse Server-Sent Events stream
lifespan Application startup/shutdown lifecycle

A Simple HTTP Example

Here's "Hello World" in raw PAGI:

use Future::AsyncAwait;

async sub app ($scope, $receive, $send) {
    die "Unsupported: $scope->{type}" if $scope->{type} ne 'http';

    await $send->({
        type    => 'http.response.start',
        status  => 200,
        headers => [['content-type', 'text/plain']],
    });

    await $send->({
        type => 'http.response.body',
        body => 'Hello from PAGI!',
        more => 0,
    });
}
Enter fullscreen mode Exit fullscreen mode

Run it:

pagi-server --app app.pl --port 5000
curl http://localhost:5000/
# => Hello from PAGI!
Enter fullscreen mode Exit fullscreen mode

The response is split into http.response.start (headers) and http.response.body (content). This separation enables streaming—send multiple body chunks with more => 1 before the final more => 0.

WebSocket Support

WebSockets are first-class citizens in PAGI:

async sub app ($scope, $receive, $send) {
    if ($scope->{type} eq 'websocket') {
        await $send->({ type => 'websocket.accept' });

        while (1) {
            my $event = await $receive->();

            if ($event->{type} eq 'websocket.receive') {
                my $msg = $event->{text} // $event->{bytes};
                await $send->({
                    type => 'websocket.send',
                    text => "Echo: $msg",
                });
            }
            elsif ($event->{type} eq 'websocket.disconnect') {
                last;
            }
        }
    }
    else {
        die "Unsupported: $scope->{type}";
    }
}
Enter fullscreen mode Exit fullscreen mode

The event loop pattern is consistent across all connection types: await events from $receive, send responses via $send.

PSGI Compatibility

One of PAGI's key features is backward compatibility with PSGI. The PAGI::App::WrapPSGI adapter lets you run existing PSGI applications on a PAGI server:

use PAGI::App::WrapPSGI;

# Your existing Catalyst/Dancer/Plack app
my $psgi_app = MyApp->psgi_app;

my $wrapper = PAGI::App::WrapPSGI->new(psgi_app => $psgi_app);
$wrapper->to_app;
Enter fullscreen mode Exit fullscreen mode

The wrapper handles all the translation: building %env from PAGI scope, collecting request bodies, and converting responses back to PAGI events.

This means you can:

  • Run legacy applications on a PAGI server
  • Add WebSocket endpoints alongside existing routes
  • Migrate incrementally from PSGI to PAGI
  • Share connection pools between old and new code

PAGI::Simple Micro-Framework

For rapid development, PAGI ships with a micro-framework inspired by Express.js:

use PAGI::Simple;

my $app = PAGI::Simple->new(name => 'My API');

$app->get('/' => sub ($c) {
    $c->text('Hello, World!');
});

$app->get('/users/:id' => sub ($c) {
    my $id = $c->path_params->{id};
    $c->json({ user_id => $id });
});

$app->post('/api/data' => sub ($c) {
    my $data = $c->json_body;
    $c->json({ received => $data, status => 'ok' });
});

$app->to_app;
Enter fullscreen mode Exit fullscreen mode

WebSockets are equally clean:

$app->websocket('/chat' => sub ($ws) {
    $ws->on(message => sub ($data) {
        $ws->broadcast("Someone said: $data");
    });
});
Enter fullscreen mode Exit fullscreen mode

PAGI::Simple includes:

  • Express-style routing with path parameters
  • JSON request/response helpers
  • Session management
  • Middleware support (CORS, logging, rate limiting, etc.)
  • Static file serving
  • WebSocket rooms and broadcasting
  • SSE channels with pub/sub

Current Status

PAGI is currently in early beta. The test suite passes, the examples work, but it hasn't been battle-tested in production.

What exists today:

  • Complete PAGI specification
  • Reference server implementation (PAGI::Server)
  • PAGI::Simple micro-framework
  • 13 example applications
  • PSGI compatibility layer
  • 483 passing tests

What it needs:

  • Developers willing to experiment and provide feedback
  • Real-world testing
  • Framework authors interested in building on PAGI
  • Performance profiling and optimization

Getting Started

git clone https://github.com/jjn1056/pagi.git
cd pagi
cpanm --installdeps .
prove -l t/

# Try the examples
pagi-server --app examples/01-hello-http/app.pl --port 5000
pagi-server --app examples/simple-01-hello/app.pl --port 5000
Enter fullscreen mode Exit fullscreen mode

Why This Matters

Perl has excellent async primitives (IO::Async, Future::AsyncAwait), but no shared specification for async web applications. Each framework implements its own approach, which limits interoperability.

PAGI provides that shared foundation. By standardizing on a common interface:

  • Servers can focus on performance and protocol handling
  • Frameworks can focus on developer experience
  • Middleware becomes portable across implementations
  • The ecosystem can grow together rather than in isolation

If you're interested in the future of async Perl web development, I'd love your feedback. Check out the repository, try the examples, and let me know what you think.

Repository: github.com/jjn1056/pagi


PAGI is not yet on CPAN. It's experimental software—please don't use it in production unless you really know what you're doing.

Top comments (0)