Skip to content

Configuring Middleware with Parameters

Different routes often need the same middleware but with different settings—stricter rate limits on expensive endpoints, different required roles for admin sections. You can pass parameters to middleware by instantiating it yourself instead of letting the container do it.

When you pass a class name, the container resolves the middleware with default constructor values:

app()->get('/api/users',
handler: fn() => User::all(),
middleware: [AuthMiddleware::class]
);

To customize the configuration, pass an instance:

app()->get('/api/users',
handler: fn() => User::all(),
middleware: [new ThrottleMiddleware(limit: 100)]
);

Writing Middleware That Accepts Parameters

Section titled “Writing Middleware That Accepts Parameters”

Accept configuration through constructor parameters with sensible defaults:

<?php
namespace App\Middleware;
use Verge\Http\Request;
use Verge\Http\Response;
class ThrottleMiddleware
{
public function __construct(
private int $limit = 60,
private int $window = 60
) {}
public function __invoke(Request $req, callable $next): Response
{
// Rate limiting logic using $this->limit and $this->window
return $next($req);
}
}

Applying Different Configurations Per Route

Section titled “Applying Different Configurations Per Route”

You need strict rate limits on expensive operations but relaxed limits on simple reads:

<?php
use App\Models\User;
app()
->get('/api/users',
handler: fn() => User::all(),
middleware: [new ThrottleMiddleware(limit: 100)]
)
->get('/api/expensive',
handler: fn() => expensiveOperation(),
middleware: [new ThrottleMiddleware(limit: 10, window: 300)]
);

Each route gets its own throttle configuration.

Using Parameterized Middleware with Groups

Section titled “Using Parameterized Middleware with Groups”

Parameterized middleware works on route groups too:

<?php
use App\Middleware\CorsMiddleware;
use App\Middleware\ThrottleMiddleware;
use App\Models\Post;
use App\Models\User;
app()->group('/api', function($app) {
$app->get('/users', fn() => User::all())
->get('/posts', fn() => Post::all());
})->use(CorsMiddleware::class)
->use(new ThrottleMiddleware(limit: 100));

Role-based access control needs different roles for different routes. Accept the required role as a parameter:

<?php
namespace App\Middleware;
use Verge\Http\Request;
use Verge\Http\Response;
use function Verge\json;
class RequireRoleMiddleware
{
public function __construct(private string $role) {}
public function __invoke(Request $req, callable $next): Response
{
$user = $req->getAttribute('user');
if (!$user || !$user->hasRole($this->role)) {
return json(['error' => 'Forbidden'], 403);
}
return $next($req);
}
}

Now each route specifies its required role:

app()
->get('/admin', handler: fn() => 'admin', middleware: [new RequireRoleMiddleware('admin')])
->get('/editor', handler: fn() => 'editor', middleware: [new RequireRoleMiddleware('editor')]);