NAME
PAGI::FastAPI - Asynchronous, Type-Safe Micro-Framework with Dependency Injection and OpenAPI & Swagger UI
VERSION
Version v0.0.7
SYNOPSIS
use v5.36;
use PAGI::FastAPI;
use PAGI::FastAPI::Depends qw(Depends);
use Types::Standard qw(Int Str);
use Future::AsyncAwait;
my $app = PAGI::FastAPI->new(
title => 'Store Microservice',
version => '1.0.0',
);
# 1. Add CORS Support
$app->add_cors(
allow_origins => ['https://example.com'],
allow_methods => ['GET', 'POST'],
);
# 2. Add Authentication Middleware Hook
# (hand-rolled here for illustration only, for ready-made schemes,
# including proper 401 challenges, see PAGI::FastAPI::Security)
$app->add_middleware(async sub ($c, $next) {
my $auth = $c->header('Authorization') // '';
if ($auth ne 'Bearer secret_token') {
$c->status(401);
return { detail => 'Unauthorized' };
}
$c->stash->{user_id} = 42;
return await $next->($c);
});
# 3. Register Lifespan Handlers
$app->on_startup(async sub {
warn "Connecting to database connection pool...\n";
});
$app->on_shutdown(async sub {
warn "Closing database connections...\n";
});
# 4. Declare Async Dependencies
my $get_db = async sub ($c) {
return { db_name => 'production_db' };
};
my $get_current_user = async sub ($c) {
my $token = $c->header('Authorization') // '';
unless ($token eq 'Bearer secret_token') {
$c->status(401);
return { detail => 'Invalid credentials' };
}
return { user_id => 42, role => 'admin' };
};
# 5. Route using HashRef Dependency Map
$app->get('/profile',
dependencies => {
db => $get_db,
user => $get_current_user,
},
handler => async sub ($c) {
my $db = $c->stash->{db};
my $user = $c->stash->{user};
return { user => $user, db => $db->{db_name} };
}
);
# 6. Route using Depends() Array Spec
$app->get('/admin',
dependencies => [
Depends($get_current_user, key => 'user'),
async sub ($c) {
if ($c->stash->{user}{role} ne 'admin') {
$c->status(403);
return { detail => 'Admin privileges required' };
}
}
],
handler => async sub ($c) {
return { message => 'Welcome to admin panel' };
}
);
# 7. Non-blocking GET route with path parameter & query validation
$app->get('/items/{id}',
query => { limit => Int },
handler => async sub ($c) {
return {
item_id => $c->param('id'),
limit => $c->param('limit'),
status => 'active',
};
}
);
# 8. Non-blocking POST route with JSON payload validation
$app->post('/items',
body => { name => Str, price => Int },
handler => async sub ($c) {
return {
created => 1,
name => $c->body('name'),
price => $c->body('price'),
};
}
);
# 9. Non-blocking WebSocket Endpoint
$app->websocket('/ws', handler => async sub ($ws, $deps) {
await $ws->accept;
while (my $msg = await $ws->receive_text) {
await $ws->send_text("Echo: $msg");
}
});
# 10. Authentication via the companion PAGI::FastAPI::Security distribution
# (extraction only, you supply the verification logic)
#
# use PAGI::FastAPI::Security::HTTPBearer;
# my $bearer = PAGI::FastAPI::Security::HTTPBearer->new;
# $app->get('/secure',
# dependencies => [ $bearer->depends(key => 'token') ],
# handler => async sub ($c) {
# return { token => $c->stash->{token} };
# }
# );
#
# See L<PAGI::FastAPI::Security> for HTTP Basic, API Key
# (header/query/cookie), and OAuth2 password-bearer schemes.
my $pagi_app = $app->to_app;
DESCRIPTION
PAGI::FastAPI is an asynchronous micro-framework for modern Perl (5.36+) inspired by Python's FastAPI.
It combines non-blocking async execution via Future::AsyncAwait on the PAGI (Perl Asynchronous Gateway Interface) specification with request parameter validation via Type::Tiny and automatic interactive documentation generation.
Key Features
PAGI Protocol Engine: Asynchronous and non-blocking natively, built for scalable web applications.
Sub-App & Static Mounting: Mount external PAGI applications, file drivers, or sub-routers using "mount".
WebSocket Support: Full non-blocking WebSocket handshake and frame streaming via PAGI::FastAPI::WebSocket.
Automatic Type Validation: Request query parameters and JSON payloads are checked against Type::Tiny constraints before reaching route handlers.
Automatic Interactive Docs: Serves an interactive Swagger UI interface at
/docsand machine-readable OpenAPI 3.1 JSON at/openapi.json.HTTP 422 Interception: Automatically intercepts invalid or missing parameters and returns formatted JSON errors with an
HTTP 422 Unprocessable Entitystatus code.Pluggable Authentication: Authentication is implemented as ordinary dependencies and middleware, with no framework lock-in. For ready-made schemes (HTTP Bearer, HTTP Basic, API Key, OAuth2 password bearer), see the companion distribution PAGI::FastAPI::Security, see "AUTHENTICATION AND SECURITY" below.
METHODS
new(%options)
my $app = PAGI::FastAPI->new(
title => 'My API',
version => '1.2.3',
);
Instantiates a new PAGI::FastAPI instance. Acceptable options:
title- (Optional) Title string for the application and OpenAPI specification. Default:'PAGI::FastAPI Application'.version- (Optional) Version string for the OpenAPI specification. Default:'$VERSION'.
get($path, %options)
post($path, %options)
put($path, %options)
patch($path, %options)
delete($path, %options)
Registers a route for the specified HTTP verb. Options include:
query- (Optional) HashRef mapping query string keys to Type::Tiny type constraints.body- (Optional) HashRef mapping request body keys to Type::Tiny type constraints. The request body is parsed as JSON by default; if theContent-Typeheader isapplication/x-www-form-urlencoded, it is parsed as form-urlencoded data instead. Either way, the same type constraints and validation apply.dependencies- (Optional) HashRef or ArrayRef of dependency code blocks or PAGI::FastAPI::Depends specs.handler- (Required) Anasync sub ($c)code reference executing business logic. Receives a PAGI::FastAPI::Context instance.
mount($path_prefix, $pagi_app)
$app->mount('/css', PAGI::App::File->new(root => './public/css')->to_app);
$app->mount('/api/v2', $v2_sub_app);
Mounts a standalone PAGI application closure or sub-application under the given path prefix. Under the hood, to_app() composes mounted applications using PAGI::App::URLMap.
on_startup($code_ref)
on_shutdown($code_ref)
on_event($event_type, $code_ref)
Registers async callbacks for PAGI Lifespan Protocol events ('startup' or 'shutdown').
add_middleware($code_ref)
Registers an asynchronous middleware with the application. Signature:
$app->add_middleware(async sub ($c, $next) {
# Pre-processing...
my $res = await $next->($c);
# Post-processing...
return $res;
});
add_cors(%options)
Enables Cross-Origin Resource Sharing (CORS) with automatic handling of OPTIONS preflight requests. Options include:
allow_origins- ArrayRef or string of allowed origins (default:['*']).allow_methods- ArrayRef or string of allowed HTTP methods (default:['*']).allow_headers- ArrayRef or string of allowed HTTP headers (default:['*']).allow_credentials- Boolean enabling credentials support (default:0).max_age- Preflight cache max age in seconds (default:600).
websocket($path, %options)
$app->websocket('/ws/{room}',
handler => async sub ($ws, $deps) {
await $ws->accept;
while (defined(my $msg = await $ws->receive_text)) {
await $ws->send_text("Room $ws->path_params->{room}: $msg");
}
}
);
Registers a WebSocket endpoint at $path. The handler receives a PAGI::FastAPI::WebSocket instance and an optional HashRef of resolved dependencies.
to_app()
my $pagi_closure = $app->to_app;
Generates and returns an asynchronous code reference conforming to the PAGI protocol specification. If sub-applications were registered via "mount", to_app() automatically wraps the routes using PAGI::App::URLMap.
AUTO-GENERATED ENDPOINTS
PAGI::FastAPI automatically registers the following system endpoints:
GET /docs- Serves an interactive Swagger UI web viewer in the browser.GET /openapi.json- Serves the generated OpenAPI 3.1 schema.
ERROR HANDLING
When a request fails parameter validation (either query string or JSON/form-urlencoded body), PAGI::FastAPI short-circuits handler execution and returns HTTP 422 Unprocessable Entity with a JSON body:
{
"detail": "Query param 'priority' invalid: Undef did not pass type constraint"
}
Unmatched routes return HTTP 404 Not Found with {"detail": "Not Found"}.
AUTHENTICATION AND SECURITY
PAGI::FastAPI has no authentication built in by design, auth needs vary too much between applications to standardise, so the framework gives you two general-purpose building blocks instead:
Middleware (
add_middleware), runs for every request, good for a single global auth check.Dependencies (the
dependenciesroute option, or PAGI::FastAPI::Depends), runs per-route, good for auth that varies by endpoint (e.g. some routes public, some requiring a token, some requiring a specific role).
A dependency signals an auth failure the same way any other dependency signals failure: by calling $c->status($code) with a code >= 400 and returning a body HashRef, which short-circuits the route handler before it runs:
my $get_current_user = async sub ($c) {
my $token = $c->header('Authorization') // '';
unless ($token eq 'Bearer secret_token') {
$c->status(401);
return { detail => 'Invalid credentials' };
}
return { user_id => 42, role => 'admin' };
};
Note that a dependency must signal failure this way, not by dieing, dependency execution is not wrapped in an eval, so a dying dependency propagates as an uncaught exception instead of a clean HTTP response.
Ready-made schemes: PAGI::FastAPI::Security
Writing the token-extraction and 401/403-response boilerplate above by hand for every scheme gets repetitive. The companion distribution PAGI::FastAPI::Security provides ready-made, Depends()-compatible classes for the common HTTP authentication schemes, modelled on Python FastAPI's fastapi.security module:
PAGI::FastAPI::Security::HTTPBearer -
Authorization: Bearer <token>, with a proper401+WWW-Authenticate: Bearerchallenge on failure.PAGI::FastAPI::Security::HTTPBasic -
Authorization: Basic <base64>, with a401+WWW-Authenticate: Basic realm="..."> challenge.PAGI::FastAPI::Security::APIKey - an API key read from a header, query string parameter, or cookie, with a
403on failure.PAGI::FastAPI::Security::OAuth2::PasswordBearer - OAuth2 bearer-token extraction plus
token_url/scopesmetadata for future OpenAPIsecuritySchemesgeneration.
Each scheme only extracts the credential, it deliberately does not verify it, so you aren't locked into one JWT library, password-hashing scheme, or identity provider. Pair it with your own verification as a second dependency:
use PAGI::FastAPI::Security::HTTPBearer;
use PAGI::FastAPI::Depends qw(Depends);
my $bearer = PAGI::FastAPI::Security::HTTPBearer->new;
$app->get('/items',
dependencies => [
$bearer->depends(key => 'token'),
Depends(async sub ($c) {
my $claims = eval { verify_jwt($c->stash->{token}) };
unless ($claims) {
$c->status(401);
return { detail => 'Invalid or expired token' };
}
return $claims;
}, key => 'claims'),
],
handler => async sub ($c) {
return { user_id => $c->stash->{claims}{sub} };
},
);
Every scheme also accepts auto_error => 0, resolving to undef instead of short-circuiting, for routes that behave differently for authenticated vs. anonymous requests. See PAGI::FastAPI::Security for the full documentation and an end-to-end JWT-verification example.
SEE ALSO
PAGI - Perl Asynchronous Gateway Interface specification.
PAGI::App::URLMap - Routing middleware for prefix-matching PAGI applications.
PAGI::FastAPI::WebSocket - Asynchronous WebSocket object for PAGI::FastAPI.
PAGI::FastAPI::Context - Context object passed to route handlers.
PAGI::FastAPI::Depends - Dependency injection helper.
PAGI::FastAPI::Security - Ready-made authentication schemes (HTTP Bearer, HTTP Basic, API Key, OAuth2 password bearer) for
dependencies.DBIx::Class::Async - Async DBIx::Class integration; see eg/dbic_async_integration.pl for a worked example with this framework.
Type::Tiny - Efficient Perl type constraint system.
Future::AsyncAwait - Async/Await syntax for Perl.
AUTHOR
Mohammad Sajid Anwar, <mohammad.anwar at yahoo.com>
REPOSITORY
https://github.com/manwar/PAGI-FastAPI
BUGS
Please report any bugs or feature requests through the web interface at https://github.com/manwar/PAGI-FastAPI/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc PAGI::FastAPI
You can also look for information at:
BUG Report
CPAN Ratings
Search MetaCPAN
LICENSE AND COPYRIGHT
Copyright (C) 2026 Mohammad Sajid Anwar.
This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License (2.0). You may obtain a copy of the full license at: