Routing Module Guide
CorianderPHP's routing system maps incoming HTTP requests to callbacks, controllers, or views. By default, controller names and the built-in router handle most paths automatically.
Custom route definitions start in public/routes.php. For small projects such as SPAs, marketing websites, brochure websites, and simple landing pages, keeping routes in this single file is usually the clearest option.
For larger applications, split route groups into app-owned files under src/Routes/ and include them from public/routes.php.
Small Project Routes
Custom routes are defined in public/routes.php. The front controller bootstraps the router and passes an instance to this file, so you can add routes directly:
use CorianderCore\Core\Router\Router;
use Nyholm\Psr7\ServerRequest;
/** @var Router $router */
$router->get('/hello/{name}', function (ServerRequest $request) {
$name = $request->getAttribute('name');
return new \Nyholm\Psr7\Response(200, [], "Hello {$name}");
});
$router->setNotFound(fn() => new \Nyholm\Psr7\Response(404, [], 'Not Found'));
The router also provides post(), put(), patch(), and delete() shortcuts. Use add($method, ...) only when the method is dynamic or uncommon.
Larger Project Route Files
Use src/Routes/ when public/routes.php becomes too large or when routes naturally split by feature area, such as admin, shop, account, or API-like web endpoints.
Create a route file with:
php coriander make:route admin
This creates:
src/Routes/admin.php
The generated file returns a closure that receives the router:
<?php
declare(strict_types=1);
use CorianderCore\Core\Router\Router;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
return static function (Router $router): void {
$router->get('admin', static function (ServerRequest $request): Response {
return new Response(200, [], 'admin route');
});
};
Register it from public/routes.php:
$adminRoutes = PROJECT_ROOT . '/src/Routes/admin.php';
if (is_file($adminRoutes)) {
(require $adminRoutes)($router);
}
Nested route files are also supported:
php coriander make:route admin/users
This creates src/Routes/admin/users.php.
Route Groups
Group routes to share a common URI prefix or middleware:
use Psr\Http\Server\MiddlewareInterface;
$auth = new class implements MiddlewareInterface {
public function process($request, $handler) {
// authentication logic
return $handler->handle($request);
}
};
$router->group('/admin', [$auth], function (Router $r) {
$r->get('/dashboard', fn (ServerRequest $req) =>
new \Nyholm\Psr7\Response(200, [], 'Dashboard'));
});
Routes inside the group inherit the /admin prefix and the $auth middleware.
Per-route Middleware
Middleware can also be attached directly when registering a route:
$router->get('/profile', fn (ServerRequest $r) =>
new \Nyholm\Psr7\Response(200, [], 'Profile'), [$auth]);
Response Handling
- Route callbacks and controller actions can return a
ResponseInterface; status code, headers, and body are preserved. - API controller actions may return arrays, which are automatically encoded as JSON responses.
- If an API action returns text output that is not valid JSON, it is wrapped as
{"data":"..."}and returned as JSON.
Error Handling
- Register a
setNotFoundcallback to handle unmatched routes gracefully. - Wrap route logic in
try/catchblocks to log and report exceptions without exposing sensitive data:
$router->post('/user', function(ServerRequest $request) {
try {
// process request
} catch (\Throwable $e) {
// log and return 500 response
}
});
Best Practices
- Group related routes into separate files and include them during bootstrap to keep definitions maintainable.
- Keep
public/routes.phpfor small custom route lists and for including route files fromsrc/Routes/. - Leverage PSR-15 middleware for cross-cutting concerns such as authentication or CSRF protection on mutating methods (
POST,PUT,PATCH,DELETE). - Use URL parameters instead of query strings for cleaner, cache-friendly routes.
- Avoid defining routes unless necessary; controllers are mapped automatically.