-
Notifications
You must be signed in to change notification settings - Fork 2
Middleware
Pair API middleware is based on the Pair\Api\Middleware interface plus Pair\Api\MiddlewarePipeline.
Middleware is the reusable request-guard layer of the Pair API stack.
Use it for cross-cutting concerns such as:
- CORS
- throttling
- bearer-token checks
- tenant or workspace headers
- feature flags or maintenance windows
Every middleware receives:
Request $requestcallable $next
and can either:
- continue the pipeline with
$next($request) - stop the request immediately by returning an API response
interface Middleware {
public function handle(Request $request, callable $next): void;
}This single method is the main method of every middleware class.
MiddlewarePipeline runs middleware in FIFO order:
- first added = first executed
- last added = closest to the final action
ApiController exposes this through:
$this->middleware(...)$this->runMiddleware(function () { ... })
Example:
protected function _init(): void
{
parent::_init();
// The default throttle is already mounted by ApiController.
$this->middleware(new \Pair\Api\CorsMiddleware());
$this->middleware(new RequireBearerMiddleware());
}
public function ordersAction(): void
{
$this->runMiddleware(function () {
// Runs only after all middleware pass.
\Pair\Api\ApiResponse::respond(['ok' => true]);
});
}Important: ApiController currently mounts the default throttle inside parent::_init(), so middleware added later in _init() runs after that throttle.
If you need a different order, override registerDefaultMiddleware().
protected function registerDefaultMiddleware(): void
{
// Puts CORS before the throttle for this controller family.
$this->middleware(new \Pair\Api\CorsMiddleware());
$this->middleware(new \Pair\Api\ThrottleMiddleware(60, 60));
}Registers one middleware in the controller pipeline.
Executes the middleware stack and then the final action callback.
These are the two API-controller methods you will use most often when working with middleware.
Typical responsibilities:
- emits
Access-Control-*headers - handles preflight
OPTIONSrequests - can short-circuit with HTTP
204
Uses RateLimiter to restrict request frequency by the best available identity:
sid- bearer token
- authenticated user
- client IP
Example:
// Allows up to 120 requests every 60 seconds.
new \Pair\Api\ThrottleMiddleware(120, 60);When the limit is exceeded, Pair returns TOO_MANY_REQUESTS with HTTP 429, Retry-After, and X-RateLimit-* headers.
use Pair\Api\Middleware;
use Pair\Api\Request;
use Pair\Api\ApiResponse;
class RequireJsonMiddleware implements Middleware {
public function handle(Request $request, callable $next): void
{
// Stops the pipeline if the payload is not JSON.
if (!$request->isJson()) {
ApiResponse::error('UNSUPPORTED_MEDIA_TYPE');
}
// Continues with the next middleware or final action.
$next($request);
}
}use Pair\Api\Middleware;
use Pair\Api\Request;
use Pair\Api\ApiResponse;
class RequireBearerMiddleware implements Middleware {
public function handle(Request $request, callable $next): void
{
// Requires the Authorization: Bearer header.
if (!$request->bearerToken()) {
ApiResponse::error('AUTH_TOKEN_MISSING');
}
// Continues only when the token is present.
$next($request);
}
}class RequireTenantMiddleware implements \Pair\Api\Middleware {
public function handle(\Pair\Api\Request $request, callable $next): void
{
// Reads the custom tenant header.
$tenant = $request->header('X-Tenant-Id');
if (!$tenant) {
// Stops the request with a normalized API error.
\Pair\Api\ApiResponse::error('BAD_REQUEST', [
'detail' => 'Missing X-Tenant-Id header',
]);
}
// Continues the pipeline when the header is present.
$next($request);
}
}protected function _init(): void
{
parent::_init();
// Adds CORS after the default throttle.
$this->middleware(new \Pair\Api\CorsMiddleware());
// Requires a bearer token for every action in this controller.
$this->middleware(new RequireBearerMiddleware());
}
public function meAction(): void
{
$this->runMiddleware(function () {
// The destination runs only after all middleware pass.
\Pair\Api\ApiResponse::respond(['ok' => true]);
});
}Middleware itself has only one public contract method, but the surrounding pipeline is equally important:
-
MiddlewarePipeline::add(Middleware $middleware): staticAppends one middleware to the stack. -
MiddlewarePipeline::run(Request $request, callable $destination): voidExecutes the full FIFO pipeline.
- Calling
$next()more than once inside the same middleware. - Forgetting to call
$next($request)when the middleware should allow the request through. - Adding a second throttle middleware without accounting for the default one already added by
ApiController. - Assuming CORS runs before the default throttle when you only append middleware after
parent::_init(). - Putting CORS after auth/throttle when preflight requests should short-circuit first.
See also: MiddlewarePipeline, RateLimiter, ThrottleMiddleware, Request, API.