One route. No guessing.
Match a method and path, resolve explicit arguments, return a PSR-7 response.
Define routes
use Icom\Http\Response;
use Psr\Http\Message\ServerRequestInterface as Request;
$app->get('/hello/{name}',
fn (Request $request, string $name) =>
Response::json(['hello' => $name])
)->name('hello');
$app->post('/notes', [NoteController::class, 'store']);
$app->put('/notes/{id}', [NoteController::class, 'replace']);
$app->patch('/notes/{id}', [NoteController::class, 'update']);
$app->delete('/notes/{id}', [NoteController::class, 'destroy']);Generate URLs
$url = $app->router->url('hello', ['name' => 'ສະບາຍດີ']);
// Values are percent-encoded, never concatenated into route patterns.Predictable matching
Static routes take precedence for the same method. Dynamic routes match in registration order. Parameters match one path segment and are decoded once. Routes are case-sensitive, with exact trailing-slash matching. GET supports HEAD with an empty body. Explicit HEAD and OPTIONS routes override automatic behavior. Unknown paths return 404; other methods return 405 with Allow. Automatic OPTIONS returns 204. Avoid slash characters inside route parameters: web servers differ in handling encoded slashes.
Controllers and contracts
Controllers may be closures, invokable class names or [Controller::class, method] arrays. Class-typed arguments use dependency injection. Named string and integer arguments come from route parameters; invalid integer input returns 400. Controllers must return ResponseInterface. Registration belongs at bootstrap, before the first request.
Inspect the route table
php bin/icom routes