Skip to content

Middleware

Jean-Marc Strauven edited this page Dec 23, 2025 · 1 revision

Middleware

Learn about the middleware that powers Laravel ApiRoute.


Overview

Laravel ApiRoute provides three middleware classes:

Middleware Purpose
ResolveApiVersion Main middleware - resolves and validates versions
EnforceApiVersion Force a specific version
TrackApiUsage Track API usage statistics
RateLimitApiVersion Version-specific rate limiting

ResolveApiVersion

The main middleware that handles version resolution.

What It Does

  1. Resolves the requested API version
  2. Validates the version exists
  3. Checks sunset status
  4. Stores version in request
  5. Dispatches events for deprecated versions
  6. Adds response headers

Flow

Request
    │
    ▼
┌─────────────────────────────┐
│  1. Resolve version         │
│     (URI/Header/Query)      │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  2. Version exists?         │──No──→ VersionNotFoundException
└─────────────────────────────┘
    │ Yes
    ▼
┌─────────────────────────────┐
│  3. Is sunset?              │──Yes─→ VersionSunsetException
└─────────────────────────────┘        (if action = 'reject')
    │ No
    ▼
┌─────────────────────────────┐
│  4. Store in request        │
│     - api_version           │
│     - api_version_definition│
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  5. Is deprecated?          │──Yes─→ Dispatch DeprecatedVersionAccessed
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  6. Execute controller      │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  7. Add response headers    │
└─────────────────────────────┘
    │
    ▼
Response

Source Code

class ResolveApiVersion
{
    public function __construct(
        private readonly VersionResolverInterface $resolver,
        private readonly VersionHeaders $headers
    ) {}

    public function handle(Request $request, Closure $next): Response
    {
        // 1. Resolve the requested version
        $version = $this->resolver->resolve($request);

        // 2. Verify that the version exists
        if ($version === null) {
            throw new VersionNotFoundException(
                $this->resolver->getRequestedVersion($request)
            );
        }

        // 3. Check if sunset
        if ($version->isSunset() && config('apiroute.sunset.action') === 'reject') {
            throw new VersionSunsetException($version);
        }

        // 4. Store the version in the request
        $request->attributes->set('api_version', $version->name());
        $request->attributes->set('api_version_definition', $version);

        // 5. Dispatch event if deprecated version
        if ($version->isDeprecated()) {
            event(new DeprecatedVersionAccessed($version, $request));
        }

        // 6. Execute the request
        $response = $next($request);

        // 7. Add version headers
        return $this->headers->addToResponse($response, $version);
    }
}

EnforceApiVersion

Force a specific API version regardless of request.

Usage

// In routes/api.php
Route::middleware('api.version:v2')->group(function () {
    Route::get('users', [UserController::class, 'index']);
});

Use Cases

  • Force all routes to use a specific version
  • Override version detection for specific endpoints
  • Testing and debugging

TrackApiUsage

Optional middleware for tracking API usage statistics.

Enable Tracking

// config/apiroute.php
'tracking' => [
    'enabled' => true,
    'driver' => 'database',
    'table' => 'api_version_stats',
    'aggregate' => 'hourly',
],

What It Tracks

  • Version used
  • Endpoint accessed
  • HTTP method
  • Response status (success/error)
  • Timestamp

Async Processing

Tracking is done asynchronously to avoid impacting response time:

dispatch(function () use ($request, $response) {
    $this->tracker->track(
        version: $request->attributes->get('api_version'),
        endpoint: $request->path(),
        method: $request->method(),
        status: $response->status()
    );
})->afterResponse();

RateLimitApiVersion

Apply version-specific rate limiting.

Configuration

ApiRoute::version('v1', function () {
    // ...
})->rateLimit(100);  // 100 requests per minute

ApiRoute::version('v2', function () {
    // ...
})->rateLimit(1000); // 1000 requests per minute

Use Case

Encourage migration from deprecated versions by applying stricter rate limits.


Middleware Configuration

Middleware Alias

// config/apiroute.php
'middleware' => [
    'group' => 'api',            // Apply to this middleware group
    'alias' => 'api.version',    // Middleware alias
],

Manual Registration

If auto-discovery is disabled, register manually:

// In bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'api.version' => \Grazulex\ApiRoute\Middleware\ResolveApiVersion::class,
    ]);
})

Or in app/Http/Kernel.php (Laravel 10):

protected $middlewareAliases = [
    'api.version' => \Grazulex\ApiRoute\Middleware\ResolveApiVersion::class,
];

Accessing Version in Controllers

After the middleware runs, version information is available:

Via Request Attributes

public function index(Request $request)
{
    $version = $request->attributes->get('api_version'); // 'v1'
    $definition = $request->attributes->get('api_version_definition'); // VersionDefinition
}

Via Request Macros

public function index(Request $request)
{
    $version = $request->apiVersion(); // 'v1'
    $status = $request->apiVersionStatus(); // VersionStatus::Active
    $definition = $request->apiVersionDefinition(); // VersionDefinition

    if ($request->isDeprecatedVersion()) {
        // Handle deprecated version
    }
}

Via Helper Functions

public function index()
{
    $version = api_version(); // 'v1'
    $definition = api_version_definition(); // VersionDefinition
}

Custom Middleware

Create custom middleware that extends the base:

<?php

namespace App\Http\Middleware;

use Closure;
use Grazulex\ApiRoute\Middleware\ResolveApiVersion;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class CustomApiVersionMiddleware extends ResolveApiVersion
{
    public function handle(Request $request, Closure $next): Response
    {
        // Pre-processing
        Log::info('API request', [
            'path' => $request->path(),
            'ip' => $request->ip(),
        ]);

        // Call parent middleware
        $response = parent::handle($request, $next);

        // Post-processing
        $response->headers->set('X-Custom-Header', 'value');

        return $response;
    }
}

Middleware Order

The recommended middleware order:

Route::middleware([
    'api',
    'api.version',      // Resolve version first
    'throttle:api',     // Then rate limit
    'auth:sanctum',     // Then authenticate
])->group(function () {
    // Routes...
});

Next Steps

Clone this wiki locally