Routing
Basic Routing
The most basic Slenix routes accept a URI and a closure, providing a very simple and expressive method of defining routes and behavior without complicated routing configuration files:
use Slenix\Http\Routing\Router;
Router::get('/greeting', function ($request, $response) {
return 'Hello World';
});The Default Route File
All Slenix routes are defined in your route files, which are located in the routes/ directory. The routes/web.php file is loaded automatically by the framework's kernel. The routes defined in routes/web.php may be accessed by entering the defined route's URL in your browser:
// routes/web.php
use Slenix\Http\Routing\Router;
use App\Controllers\HomeController;
Router::get('/', [HomeController::class, 'index']);Available Router Methods
The router allows you to register routes that respond to any HTTP verb:
Router::get($uri, $callback);
Router::post($uri, $callback);
Router::put($uri, $callback);
Router::patch($uri, $callback);
Router::delete($uri, $callback);
Router::options($uri, $callback);Sometimes you may need to register a route that responds to multiple HTTP verbs. You may do so using the match method. Or, you may even register a route that responds to all HTTP verbs using the any method:
Router::match(['GET', 'POST'], '/contact', function ($request, $response) {
// handles both GET and POST
});
Router::any('/endpoint', function ($request, $response) {
// handles any HTTP verb
});Route Parameters
Required Parameters
Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters:
Router::get('/users/{id}', function ($request, $response, $params) {
return 'User ' . $params['id'];
});You may define as many route parameters as required by your route:
Router::get('/posts/{post}/comments/{comment}', function ($request, $response, $params) {
$postId = $params['post'];
$commentId = $params['comment'];
});Route parameters are injected into route callbacks and controllers via the third argument $params, which is an associative array keyed by parameter name.
Optional Parameters
Occasionally you may need to specify a route parameter that may not always be present in the URI. You may do so by placing a ? mark after the parameter name:
Router::get('/users/{id?}', function ($request, $response, $params) {
$id = $params['id'] ?? null;
if (!$id) {
return 'Listing all users';
}
return 'User ' . $id;
});Parameter Constraints
You may constrain the format of your route parameters using the whereNumber, whereAlpha, and whereAlphaNumeric methods on a route instance:
Router::get('/users/{id}', [UserController::class, 'show'])
->whereNumber('id');
Router::get('/categories/{slug}', [CategoryController::class, 'show'])
->whereAlpha('slug');
Router::get('/posts/{slug}', [PostController::class, 'show'])
->whereAlphaNumeric('slug');| Method | Allowed Characters | Example |
|---|---|---|
whereNumber | Digits only | 42, 100 |
whereAlpha | Letters only | news, about |
whereAlphaNumeric | Letters and digits | post1, abc123 |
Named Routes
Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the name method onto the route definition:
Router::get('/dashboard', [DashboardController::class, 'index'])
->name('dashboard');You may also specify route names for controller actions:
Router::get('/users/{id}', [UserController::class, 'show'])
->name('users.show');
Router::post('/users', [UserController::class, 'store'])
->name('users.store');Generating URLs to Named Routes
Once you have assigned a name to a given route, you may use the route's name when generating URLs via Slenix's route helper function or Router::route method:
// Generating a URL
$url = route('dashboard');
// Generating a URL with parameters
$url = route('users.show', ['id' => 42]);
// Result: /users/42In your Luna templates:
<a href="{{ route('dashboard') }}">Dashboard</a>
<a href="{{ route('users.show', ['id' => $user['id']]) }}">View Profile</a>If the named route defines parameters and those parameters are not provided, a RuntimeException will be thrown:
// This will throw a RuntimeException — 'id' is required
$url = route('users.show');Route Groups
Route groups allow you to share route attributes, such as middleware or prefixes, across a large number of routes without needing to define those attributes on each individual route.
Middleware
To assign middleware to all routes within a group, pass a middleware key in the group configuration array. Middleware are executed in the order they are listed:
Router::group(['middleware' => ['auth']], function () {
Router::get('/dashboard', [DashboardController::class, 'index']);
Router::get('/profile', [ProfileController::class, 'index']);
});You may also use the Router::middleware shorthand when you only need to assign middleware without a prefix:
Router::middleware('auth', function () {
Router::get('/profile', [ProfileController::class, 'index']);
Router::get('/settings', [ProfileController::class, 'settings']);
});Prefixes
The prefix key may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with admin:
Router::group(['prefix' => 'admin', 'middleware' => ['auth']], function () {
Router::get('/dashboard', [AdminController::class, 'dashboard']);
Router::get('/users', [AdminController::class, 'users']);
Router::post('/users', [AdminController::class, 'store']);
});The routes above are accessible at:
GET /cl-admin/dashboard
GET /cl-admin/users
POST /cl-admin/usersCombining Prefix and Middleware
You may combine both prefix and middleware keys in the same group definition. This is a common pattern for organizing protected areas of your application:
Router::group(['prefix' => 'api/v1', 'middleware' => ['auth', 'throttle']], function () {
Router::get('/users', [Api\UserController::class, 'index']);
Router::get('/users/{id}', [Api\UserController::class, 'show']);
Router::post('/users', [Api\UserController::class, 'store']);
Router::put('/users/{id}', [Api\UserController::class, 'update']);
Router::delete('/users/{id}', [Api\UserController::class, 'destroy']);
});Middleware
Assigning Middleware to Routes
Middleware may be assigned to individual routes by chaining the middleware method onto the route definition:
Router::get('/profile', [ProfileController::class, 'index'])
->middleware('auth');You may assign multiple middleware to a route by passing an array:
Router::get('/cl-admin', [AdminController::class, 'index'])
->middleware(['auth', 'throttle']);The middlewares method is an alias for middleware and works identically:
Router::get('/cl-admin', [AdminController::class, 'index'])
->middlewares(['auth', 'throttle']);Middleware Aliases
Slenix resolves the following short aliases to their full class names automatically:
| Alias | Class |
|---|---|
auth | App\Middlewares\AuthMiddleware |
guest | App\Middlewares\GuestMiddleware |
cors | App\Middlewares\CorsMiddleware |
jwt | App\Middlewares\JwtMiddleware |
throttle | App\Middlewares\ThrottleMiddleware |
You may always use the full class name instead of an alias:
Router::get('/profile', [ProfileController::class, 'index'])
->middleware(\App\Middlewares\AuthMiddleware::class);Global Middleware
If you want a middleware to run during every HTTP request to your application, you may register it as a global middleware using Router::globalMiddleware. Global middleware is prepended to every route automatically:
// routes/web.php or Kernel bootstrap
Router::globalMiddleware([
\App\Middlewares\CorsMiddleware::class,
\App\Middlewares\ThrottleMiddleware::class,
]);Writing Middleware
All middleware must implement the Slenix\Http\Middlewares\Middleware interface and define a handle method:
namespace App\Middlewares;
use Slenix\Http\Request;
use Slenix\Http\Response;
use Slenix\Http\Middlewares\Middleware;
class AuthMiddleware implements Middleware
{
public function handle(Request $request, Response $response, callable $next): mixed
{
if (!session()->has('user_id')) {
redirect('/')->with('error', 'Please sign in to continue.');
}
return $next($request, $response);
}
}Returning $next($request, $response) passes the request deeper into the application. To halt the request — for example, when authentication fails — simply redirect or abort without calling $next.
Middleware Pipeline
Slenix executes middleware in reverse declaration order, forming a pipeline identical to how Laravel handles it. The last declared middleware wraps the outermost layer of the pipeline.
Request
→ ThrottleMiddleware
→ AuthMiddleware
→ Controller action
← AuthMiddleware
← ThrottleMiddleware
ResponseCSRF Protection
Slenix automatically validates CSRF tokens for all state-mutating HTTP requests. Any route that receives a POST, PUT, PATCH, or DELETE request must include a valid CSRF token, or the request will be rejected with a 419 status code.
Including the Token in Forms
When defining HTML forms in your application, you should include the @csrf Luna directive or the csrf_field() helper so that the CSRF protection middleware can validate the request:
<form action="{{ route('login') }}" method="post">
@csrf
<!-- fields -->
</form>The @csrf directive renders a hidden input field:
<input type="hidden" name="_csrf_token" value="a1b2c3...">CSRF Tokens via JavaScript
When making requests via JavaScript (fetch, Axios, etc.), include the CSRF token as a request header. First, store the token in a meta tag:
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>Then read it in your JavaScript:
fetch('/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ name: 'Cláudio' }),
});Redirects
Router::redirect provides a convenient shortcut for defining a route that redirects to another URI. By default, a 302 status code is returned:
Router::redirect('/here', '/there');If you need a permanent redirect, use a 301 status:
Router::redirect('/old-blog', '/blog', 301);View Routes
If your route only needs to return a view, you may use the Router::view method. Like the redirect method, this method provides a simple shortcut so that you do not have to define a full route or controller:
Router::view('/about', 'about');You may pass an array of data to the view as the third argument:
Router::view('/about', 'about', [
'title' => 'About Us',
'company' => 'Slenix',
]);Accessing the Current Route
You may inspect all registered routes at any time by calling Router::getRoutes(). This returns the raw array of route definitions and is useful for debugging:
$routes = Router::getRoutes();Fallback Routes and 404 Handling
When no registered route matches the incoming request, Slenix will automatically respond with a 404 Not Found status. It searches for a custom error page in the following locations, in order:
views/errors/404.php
views/error/404.php
src/Core/Exceptions/errors/404.phpIf none of these files exist, a RuntimeException is thrown with the message 404 - Page not found.
To display a custom 404 page, create one of the above files. For example:
<!-- views/errors/404.php -->
<!DOCTYPE html>
<html>
<head><title>404 — Not Found</title></head>
<body>
<h1>Page not found.</h1>
<a href="/">Return home</a>
</body>
</html>Clearing Routes
When writing automated tests, you may want to reset all registered routes between test cases. The clearRoutes method resets the route table, prefix stack, and all middleware registrations:
Router::clearRoutes();This method clears global middleware, group middleware, and prefix state in addition to the route list. It is intended exclusively for use in test environments.
Route Method Reference
Router Static Methods
| Method | Description |
|---|---|
Router::get($uri, $handler) | Register a GET route |
Router::post($uri, $handler) | Register a POST route |
Router::put($uri, $handler) | Register a PUT route |
Router::patch($uri, $handler) | Register a PATCH route |
Router::delete($uri, $handler) | Register a DELETE route |
Router::options($uri, $handler) | Register an OPTIONS route |
Router::any($uri, $handler) | Register a route for the current HTTP verb |
Router::match($methods, $uri, $handler) | Register a route for multiple HTTP verbs |
Router::redirect($from, $to, $status) | Register a redirect route |
Router::view($uri, $view, $data) | Register a route that returns a view |
Router::group($config, $callback) | Register a group of routes |
Router::middleware($middleware, $callback) | Register a middleware-only group |
Router::globalMiddleware($middlewares) | Register global middleware |
Router::route($name, $params) | Generate a URL for a named route |
Router::getRoutes() | Return all registered routes |
Router::clearRoutes() | Clear all routes (testing only) |
Router::dispatch() | Dispatch the current HTTP request |
Route Fluent Methods
All route registration methods return a Route instance, which supports the following fluent methods:
| Method | Description | |
|---|---|---|
->name(string $name) | Assign a name to the route | |
| `->middleware(string\ | array $middleware)` | Attach middleware to the route |
| `->middlewares(string\ | array $middleware)` | Alias for middleware() |
->whereNumber(string $param) | Constrain parameter to digits only | |
->whereAlpha(string $param) | Constrain parameter to letters only | |
->whereAlphaNumeric(string $param) | Constrain parameter to letters and digits |