Middleware
Middleware lets you wrap logic around your handlers: things like adding a header to every response, logging requests, or enforcing a rate limit. In Fast, middleware is just a function that receives the request and a $next callback.
Writing middleware
The simplest middleware is a plain callable. Call $next($request) to continue down the chain, then do whatever you like with the response before returning it:
use Fast\Http\Request;
use Fast\Http\Response;
use_middleware(static function (Request $request, callable $next): Response {
$response = $next($request);
return $response->withHeader('X-Powered-By', 'Fast');
});
You can also short-circuit. If you return without calling $next, the handler never runs, which is handy for auth gates or maintenance modes:
use_middleware(static function (Request $request, callable $next): Response {
if ($request->header('x-api-key') !== $expected) {
return json(['error' => 'unauthorized'], 401);
}
return $next($request);
});
If you prefer a class, implement Fast\Middleware\Middleware and pass an instance or class name to use_middleware() (or to a route/group, below). The interface has the same shape: a method that takes the request and $next.
Global, per-route, and group middleware
use_middleware() registers global middleware: it runs for every request. When a concern belongs to only some routes, attach it directly instead.
For a single route, pass middleware: to the route helper. It accepts the same three forms as use_middleware(), a Middleware instance, a fn(Request, $next): Response callable, or a string class/container id, either singly or as an array:
get('/admin', $handler, middleware: AuthMiddleware::class);
post('/pay', $handler, middleware: [Csrf::class, Throttle::class]);
For a batch of routes that share a prefix and/or middleware, wrap them in a group(). The first argument is an options array (prefix and/or middleware); the second is a closure that registers the routes:
group(['prefix' => '/admin', 'middleware' => AuthMiddleware::class], function (): void {
get('/', fn () => 'admin home'); // => GET /admin
get('/users', fn () => 'users'); // => GET /admin/users
});
Groups nest and accumulate. An inner group's prefix appends to the outer prefix, and its middleware runs after (inside) the outer group's:
group(['prefix' => '/api', 'middleware' => ApiKey::class], function (): void {
group(['prefix' => '/v1', 'middleware' => Throttle::class], function (): void {
get('/ping', fn () => 'pong', middleware: Trace::class);
// => GET /api/v1/ping
// layers: ApiKey -> Throttle -> Trace -> handler
});
});
Ordering
Layers run strictly outer to inner:
global -> outer group -> inner group -> route -> handler
Within any one list the first entry is the outermost (it sees the request first and the response last). Route and group middleware run as a nested pipeline inside the global one, so global middleware is always outermost.
One caveat: a middleware registered both globally and on a route/group runs twice, once per pipeline. That is harmless for idempotent layers, but register a stateful one (like a session opener) in exactly one place.
Named aliases
Long middleware stacks get repetitive. Give a stack a name with middleware_alias() and reference it by that name wherever a middleware value is accepted:
middleware_alias('web', [StartSession::class, VerifyCsrf::class]);
middleware_alias('admin', ['web', RequireAdmin::class]); // aliases can nest
get('/dashboard', Dashboard::class, middleware: 'admin');
group(['prefix' => '/account', 'middleware' => 'web'], function (): void {
// ...
});
Aliases are expanded at request time, so you can define them after the routes that use them. An alias may reference other aliases; a genuine cycle throws a clear error.
Keep global middleware cheap
Because every global middleware runs on every request, you want them to be lightweight, especially for requests they do not actually care about. A middleware that does a quick path check and bails out early is fine. A middleware that does real work unconditionally, for a concern only some requests use, is a design smell in Fast. Reach for a per-route or group middleware instead.
This is the same principle behind Fast's opt-in subsystems: a request that does not touch a feature should not pay for it. Fast even asserts the size of its own global middleware stack in tests, so the framework holds itself to this rule too.
Next, let us look at the request and response objects your middleware and handlers work with, in Requests & Responses.