API Builder
Kirby Headless includes an Express-style API builder for creating custom routes with middleware support. This allows you to reuse logic like authentication, validation, and error handling across multiple routes.
API Builder Basics
The API builder is provided by the JohannSchopplich\Headless\Api\Api class. Use Api::createHandler() to chain middleware functions that execute sequentially before your final route handler.
How It Works
- Middleware functions receive a
$contextarray and route$args - Each middleware can modify the context or return a response
- If a middleware returns a
Response, execution stops - Otherwise, the next middleware executes with the updated context
Creating Routes
Basic Route
Create a simple API route accessible at /api/hello:
use JohannSchopplich\Headless\Api\Api;
return [
'routes' => [
[
'pattern' => 'api/hello',
'method' => 'GET',
'action' => Api::createHandler(
function (array $context, array $args) {
return Api::createResponse(200, [
'message' => 'Hello World'
]);
}
)
]
]
];
Route with Authentication
Protect routes using the built-in bearer token middleware:
use JohannSchopplich\Headless\Api\Api;
use JohannSchopplich\Headless\Api\Middlewares;
return [
'routes' => [
[
'pattern' => 'api/protected',
'method' => 'GET',
'action' => Api::createHandler(
Middlewares::hasBearerToken(...),
function (array $context, array $args) {
return Api::createResponse(200, [
'data' => 'Protected content'
]);
}
)
]
]
];
Extending Kirby's API
Add routes to Kirby's /api namespace:
use JohannSchopplich\Headless\Api\Api;
use JohannSchopplich\Headless\Api\Middlewares;
return [
'api' => [
'routes' => [
[
'pattern' => 'posts',
'method' => 'GET',
'auth' => false, // Disable Kirby's default auth
'action' => Api::createHandler(
Middlewares::hasBearerToken(...),
function (array $context, array $args) {
$posts = kirby()->site()->find('blog')->children();
return Api::createResponse(200, [
'posts' => $posts->map(fn ($p) => [
'title' => $p->title()->value(),
'uri' => $p->uri()
])->values()
]);
}
)
]
]
]
];
Built-in Middlewares
Kirby Headless provides several built-in middleware functions in the Middlewares class:
hasBearerToken()
Validates bearer token and redirects to Panel if missing (when headless.panel.redirect is enabled):
use JohannSchopplich\Headless\Api\Middlewares;
Api::createHandler(
Middlewares::hasBearerToken(...),
function (array $context, array $args) {
// Token is valid
}
);
hasBearerTokenWithoutRedirect()
Validates bearer token without Panel redirect:
use JohannSchopplich\Headless\Api\Middlewares;
Api::createHandler(
Middlewares::hasBearerTokenWithoutRedirect(...),
function (array $context, array $args) {
// Token is valid
}
);
tryResolveFiles()
Attempts to resolve file requests:
use JohannSchopplich\Headless\Api\Middlewares;
Api::createHandler(
Middlewares::tryResolveFiles(...),
function (array $context, array $args) {
// File resolution attempted
}
);
tryResolvePage()
Attempts to resolve page requests:
use JohannSchopplich\Headless\Api\Middlewares;
Api::createHandler(
Middlewares::tryResolvePage(...),
function (array $context, array $args) {
// Page resolution attempted
}
);
Custom Middleware
Create custom middleware functions to handle validation, data transformation, or other logic:
use JohannSchopplich\Headless\Api\Api;
// Define custom middleware
$requireDateParam = function (array $context, array $args) {
$date = kirby()->request()->get('date');
if (empty($date)) {
return Api::createResponse(400, [
'error' => 'Missing date parameter'
]);
}
// Add date to context for use in later handlers
$context['date'] = $date;
return $context;
};
return [
'routes' => [
[
'pattern' => 'api/events',
'method' => 'GET',
'action' => Api::createHandler(
$requireDateParam,
function (array $context, array $args) {
$date = $context['date'];
return Api::createResponse(200, [
'date' => $date,
'events' => [] // Your event data
]);
}
)
]
]
];
Response Format
All responses use a consistent JSON format with status code and optional result data:
{
"code": 200,
"status": "OK",
"result": {
"message": "Success"
}
}
Error responses follow the same pattern:
{
"code": 401,
"status": "Unauthorized"
}
Examples
POST Endpoint with Validation
use JohannSchopplich\Headless\Api\Api;
use JohannSchopplich\Headless\Api\Middlewares;
$validateBody = function (array $context, array $args) {
$body = kirby()->request()->body();
$data = $body->data();
if (empty($data['title'])) {
return Api::createResponse(400, [
'error' => 'Title is required'
]);
}
$context['data'] = $data;
return $context;
};
return [
'routes' => [
[
'pattern' => 'api/pages',
'method' => 'POST',
'action' => Api::createHandler(
Middlewares::hasBearerToken(...),
$validateBody,
function (array $context, array $args) {
$data = $context['data'];
// Create page logic here
return Api::createResponse(201, [
'message' => 'Page created'
]);
}
)
]
]
];