Routing & Middleware
Define routes, attach middleware, and generate URLs with the Velvet router.
Route definitions #
Routes live in routes/web.php (or modules can register their own during boot()). Each call returns a RouteDefinition, so you can fluently attach middleware or names:
$router->get('/blog/{slug}', [PostController::class, 'show'])
->middleware(['auth', 'throttle:60,1'])
->name('blog.show');
Key features:
- Methods:
get/post/put/delete/patch/any()(or arrays of verbs). - Parameters:
{slug}(single segment),{slug?}(optional),{file*}(greedy for assets). All captured params are injected after theRequestargument. - Named routes:
router->url('blog.show', ['slug' => 'hello'])builds/blog/hello. - Events:
router.matchingfires before pattern evaluation;router.matchedincludes the resolved params.
Middleware system #
config/http.php defines aliases and the global stack:
return [
'middleware' => [
'aliases' => [
'errors' => ErrorHandlingMiddleware::class,
'csrf' => VerifyCsrfToken::class,
],
'global' => [
'errors',
// e.g. 'csrf',
],
],
];
- Aliases let you refer to middleware by short names (
'auth','throttle', etc.). - Global stack is prepended to every request. Add entries here to enforce policies application-wide.
- Use
$router->middleware('csrf')or$router->middleware([ 'auth', fn($request, $next) => $next($request) ]);to attach per-route middleware.
VelvetCMS\Http\Middleware\Pipeline resolves aliases or class names through the application container, so constructor injection works for sessions, guards, etc.
Built-in middleware #
| Middleware | Purpose |
|---|---|
ErrorHandlingMiddleware |
Wraps downstream handlers, reports exceptions, and renders friendly responses via the shared exception handler. |
VerifyCsrfToken |
Validates _token or X-CSRF-TOKEN headers on POST/PUT/PATCH/DELETE, with glob-style except patterns. |
ThrottleRequests |
Rate limits requests based on IP address using global configuration. |
Rate Limiting #
VelvetCMS includes a built-in rate limiter to protect your application from abuse. It uses the configured cache driver to store hit counts.
Usage #
Apply the throttle middleware to your routes:
// Limit based on config/http.php settings
$router->get('/api/users', [UserController::class, 'index'])
->middleware('throttle');
If the limit is exceeded, a 429 Too Many Requests response is returned with Retry-After and X-RateLimit-* headers.
Configuration #
The rate limiter is configured globally in config/http.php. It does not currently support per-route limits.
// config/http.php
return [
'rate_limit' => [
'max_attempts' => 60,
'decay_minutes' => 1,
],
// ...
];
Ensure your cache is configured correctly in config/cache.php.
Add your own by implementing MiddlewareInterface::handle(Request $request, callable $next): Response or creating an invokable class. Register aliases in config to keep route files tidy.
Route cache workflow #
velvet route:cacheexecutesroutes/web.php, capturesRouter::getRouteDefinitions(), and writes them tostorage/cache/routes.php.public/index.phpwill load that array instead of re-registering routes on every request.velvet route:cleardeletes the cache file.
Always clear + rebuild when routes change (CI scripts usually run route:clear && route:cache).
Serving theme assets #
routes/web.php includes a hardened /themes/{theme}/assets/{asset*} route:
- Sanitizes the theme name + asset path to prevent traversal.
- Streams the file with
Response::file()and strong caching headers (Cache-Control,Last-Modified, and conditional 304 logic based onIf-None-Match+If-Modified-Since). - Honors
theme.assets.max_agefromconfig/theme.php.
Use it as a template for your own asset routes (e.g., module static files) or contribute a CDN-friendly variant via a module.
Tips #
- Centralize shared closures in controllers or invokable classes; the router happily resolves dependencies.
- Prefer middleware for cross-cutting concerns (auth, localization, feature flags) so routes stay declarative.
- Emit your own events (e.g.,
router.matched) within modules to monitor API usage without changing controllers.