barryvdh/laravel-stack-middleware
Adds a simple stack-style middleware manager for Laravel, letting you group, push, and compose middleware in a defined order. Useful for building reusable request/response pipelines and applying them to routes or controllers with minimal boilerplate.
Installation:
composer require barryvdh/laravel-stack-middleware
No additional configuration is required—it auto-registers with Laravel’s service provider.
First Use Case:
Define a middleware stack in app/Http/Kernel.php:
protected $middlewareStacks = [
'admin' => [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\Authenticate::class,
],
];
Apply the Stack: Use the stack in your routes:
Route::middleware(['stack:admin'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Or in a controller:
public function __construct()
{
$this->middleware('stack:admin');
}
Verify:
Run a test request to ensure the middleware stack executes as expected. Use Laravel’s built-in debugging tools like php artisan route:list or php artisan middleware:list to confirm.
Defining Stacks:
app/Http/Kernel.php under $middlewareStacks.public function boot()
{
if ($this->app->environment('production')) {
$this->app['router']->middlewareStacks['prod'] = [
\App\Http\Middleware\RateLimit::class,
\App\Http\Middleware\ThrottleRequests::class,
];
}
}
Reusing Stacks:
'api' => [
\App\Http\Middleware\ApiAuthenticate::class,
'stack:auth', // Reuse the 'auth' stack
],
public function handle($request, Closure $next)
{
if ($request->bearerToken()) {
$stack = 'api';
} else {
$stack = 'web';
}
return app(\Barryvdh\StackMiddleware\Facades\StackMiddleware::class)
->stack($stack)
->run($request, $next);
}
Testing:
$stack = \Barryvdh\StackMiddleware\Facades\StackMiddleware::stack('admin');
$this->assertCount(6, $stack->getMiddleware());
$response = $this->actingAs($user)->get('/dashboard');
$response->assertStatus(200);
Leverage Laravel’s Middleware Groups:
Combine with Laravel’s built-in $middlewareGroups for hybrid stacks:
'web' => [
\App\Http\Middleware\EncryptCookies::class,
'stack:auth', // Custom stack
\Illuminate\Session\Middleware\AuthenticateSession::class,
],
Dynamic Stacks via Facade: Use the facade to build stacks at runtime:
$stack = \Barryvdh\StackMiddleware\Facades\StackMiddleware::stack('api');
$stack->push(\App\Http\Middleware\LogIp::class);
Environment-Specific Stacks:
Define stacks in a config file (e.g., config/middleware.php) and load them in a service provider:
$this->app['router']->middlewareStacks = config('middleware.stacks');
Parallel Middleware Execution:
For non-blocking middleware (e.g., logging, analytics), use Laravel’s parallel middleware wrapper:
'analytics' => [
\App\Http\Middleware\ParallelMiddleware::class,
\App\Http\Middleware\LogRequest::class,
\App\Http\Middleware\TrackAnalytics::class,
],
Middleware Order Matters:
php artisan middleware:list to debug execution order.Circular Dependencies:
stack:auth includes stack:api, which includes stack:auth).Stack Not Found Errors:
InvalidArgumentException if a stack name is misspelled or undefined.$stack = \Barryvdh\StackMiddleware\Facades\StackMiddleware::stack('undefined', ['fallback.middleware']);
Performance Overhead:
Facade vs. Direct Usage:
StackMiddleware) is convenient but can lead to tight coupling. Prefer dependency injection for complex logic:
public function __construct(private StackMiddleware $stackMiddleware) {}
Log Stack Resolution: Add logging to trace stack execution:
\Barryvdh\StackMiddleware\Facades\StackMiddleware::enableLogging();
Logs appear in storage/logs/laravel.log.
Middleware Debugging:
Use php artisan middleware:debug to inspect the middleware pipeline:
php artisan middleware:debug --route=dashboard
Test Stacks in Isolation: Create a dedicated test stack for debugging:
'debug' => [
\App\Http\Middleware\LogRequest::class,
\App\Http\Middleware\ValidateSignature::class,
],
Apply it to a test route and inspect logs.
Stack Naming Conventions:
api-auth, admin_panel).web, api) unless extending them.Service Provider Registration:
If defining stacks dynamically, ensure the service provider boots after StackMiddlewareServiceProvider:
public function register()
{
$this->app->register(\Barryvdh\StackMiddleware\StackMiddlewareServiceProvider::class);
}
Laravel 11/12 Compatibility:
TrustProxies) may behave differently. Test thoroughly.Custom Stack Resolvers: Extend the stack resolver to support dynamic logic:
use Barryvdh\StackMiddleware\StackResolver;
class CustomStackResolver extends StackResolver
{
public function resolve($stack)
{
if ($stack === 'tenant') {
return $this->app['tenant.middleware'];
}
return parent::resolve($stack);
}
}
Bind it in a service provider:
$this->app->bind(StackResolver::class, CustomStackResolver::class);
Middleware Stack Events:
Listen for stack-related events (e.g., stack.resolved):
\Barryvdh\StackMiddleware\Facades\StackMiddleware::listen('stack.resolved', function ($stack) {
logger()->info("Stack resolved: {$stack->getName()}");
});
Parallel Middleware:
For Laravel 11+, use the parallel middleware wrapper to execute non-blocking middleware concurrently:
'analytics' => [
\Illuminate\Pipeline\ParallelMiddleware::class,
\App\Http\Middleware\LogRequest::class,
\App\Http\Middleware\TrackAnalytics::class,
],
Stack Aliases:
Create aliases for frequently used stacks in app/Providers/AppServiceProvider.php:
$this->app->alias('stack.admin', \Barryvdh\StackMiddleware\Facades\StackMiddleware::class . '@stack');
Environment-Specific Stacks: Load stacks from environment-specific config files:
$stacks = require config_path('middleware/' . config('app.env') . '.php');
$this->app['router']->middlewareStacks = $stacks;
Stack Validation: Validate stack definitions in a service provider:
public function boot()
{
$stacks =
How can I help you explore Laravel packages today?