Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 44 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20264 min read

Advanced API Versioning Strategies: Header-Based Routing in Laravel

Master API versioning and maintain backward compatibility in your distributed systems. Learn to implement header-based versioning for clean, scalable APIs.

APIVersioningArchitectureLaravelRESTphpbackend

Previously in this course, we explored distributed locks to manage state across high-concurrency environments. While locking ensures data integrity, evolving your API structure without breaking existing integrations is the next frontier of maintainability. This lesson adds a layer of architectural discipline by implementing header-based versioning to support multiple API versions side-by-side.

The Case for Header-Based Versioning

In high-traffic systems, your API is a contract. Breaking that contract forces your consumers into expensive migration cycles. While URI versioning (e.g., /api/v1/users) is standard, it often litters your routing logic and makes resource discovery (HATEOAS) more complex.

Header-based versioning—specifically using the Accept header—allows your URIs to remain clean and resource-oriented. It treats the version as a representation of the resource rather than a location.

StrategyProsCons
URI PathVisible, easy to cache, simple.Changes resource identity, breaks REST principles.
HeaderClean URIs, content negotiation.Harder to test in browsers, complex proxy caching.
Query ParamEasy to implement.Overwrites cache keys, less standard.

Implementing Versioned Routing

Laravel’s router is powerful, but it doesn't natively support "versioning" as a first-class citizen. We achieve this by creating a custom Route Matcher.

First, define a custom header, such as X-API-Version, or use the Accept header with a custom vendor type (e.g., application/vnd.myapp.v1+json). We will use the latter for better standards compliance.

1. Create the Version Matcher

We need a way to tell Laravel to route to a specific group based on the header. Create a middleware or a custom route macro. A cleaner approach for advanced architectures is using a custom Route condition.

PHP
#6A9955">// app/Providers/RouteServiceProvider.php

use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request;

public function boot()
{
    Route::macro('version', function ($version, $callback) {
        Route::group(['middleware' => "api.version:{$version}"], $callback);
    });
}

2. The Versioning Middleware

The middleware verifies the client's requested version. If the version is missing or unsupported, we can throw a 406 Not Acceptable error or default to a legacy version.

PHP
#6A9955">// app/Http/Middleware/EnsureApiVersion.php

namespace App\Http\Middleware;

use Closure;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;

class EnsureApiVersion
{
    public function handle($request, Closure $next, $version)
    {
        $requested = $request->header('Accept');

        if (!str_contains($requested, "vnd.myapp.{$version}+json")) {
            throw new NotAcceptableHttpException("Unsupported API version.");
        }

        return $next($request);
    }
}

3. Organizing Versioned Routes

With the infrastructure in place, your api.php file remains readable even as your system grows.

PHP
#6A9955">// routes/api.php

Route::version('v1', function () {
    Route::get('/users', [App\Http\Controllers\V1\UserController::class, 'index']);
});

Route::version('v2', function () {
    Route::get('/users', [App\Http\Controllers\V2\UserController::class, 'index']);
});

Managing Multiple Controllers

As you scale, avoid "fat" controllers. Since we are already using Action Classes, your versioned controllers should strictly act as entry points that resolve the correct Domain Actions.

If v2 introduces a new field for the User object, your CreateUserAction might change. Use Data Transfer Objects (DTOs) to map different input formats into a unified Domain Object that your internal services understand.

Hands-on Exercise: Versioned Response

  1. Implement the EnsureApiVersion middleware as shown above.
  2. Create two versions of a UserProfileController.
  3. In V1, return the user's name. In V2, return an object containing first_name and last_name.
  4. Use curl -H "Accept: application/vnd.myapp.v2+json" http://your-app.test/api/users to verify the response changes dynamically.

Common Pitfalls

  • Proxy Caching: CDNs like Cloudflare often cache based on the URI. If you use header-based versioning, ensure your Vary: Accept header is sent. Otherwise, a v1 request might be cached and served to a v2 client.
  • Defaulting: Never silently default to the latest version. If a client doesn't specify a version, return an error or a strictly defined legacy version to prevent unexpected behavior.
  • Over-Engineering: If your API is internal-only and small, URI versioning is significantly easier to manage. Only reach for header-based versioning when you need to maintain multiple public-facing API versions for an extended period.

Recap

We've moved beyond simple routing by implementing a robust, header-driven versioning strategy. This ensures that our Modular Monolith Structure remains flexible, allowing us to evolve domain logic for new clients while keeping legacy integrations functional. By decoupling the version from the URI, we maintain cleaner, more RESTful interfaces.

Up next: We will tackle Database Migration Strategies, where we'll learn how to apply breaking schema changes without downtime or breaking existing API versions.

Previous lessonDistributed LocksNext lesson Database Migration Strategies
Back to Blog

Similar Posts

LaravelJune 25, 20263 min read

Resource Controllers and API Responses in Laravel

Learn how to use Laravel API resources to transform model data and return consistent, clean JSON responses for your RESTful applications.

Read more
LaravelJune 28, 20264 min read

JWT and Stateless Security: Architecting Scalable API Authentication

Master stateless API authentication in Laravel. Learn to issue and verify JWTs, implement secure token rotation, and handle revocation in a high-traffic system.

Part of the course

Advanced Laravel: Architecture, Scaling & Performance

advanced · Lesson 44 of 57

  1. 1

    Transitioning from MVC to DDD

    3 min
  2. 2

    Defining Bounded Contexts

    3 min
  3. 3

    Implementing Action Classes

    3 min
Read more
LaravelJune 26, 20263 min read

Building a Search API: Integrating Drivers & Indexing in Laravel

Stop relying on slow database LIKE queries. Learn how to integrate search drivers, index Eloquent models, and build a high-performance Search API in Laravel.

Read more
4

Utilizing Data Transfer Objects (DTOs)

3 min
  • 5

    Service Layer Pattern

    4 min
  • 6

    Modular Monolith Structure

    3 min
  • 7

    Querying with Strict Eloquent

    4 min
  • 8

    Advanced Subqueries and Joins

    4 min
  • 9

    Raw Expressions for Performance

    4 min
  • 10

    Advanced Indexing Strategies

    4 min
  • 11

    Database Partitioning Techniques

    4 min
  • 12

    Read/Write Database Splitting

    4 min
  • 13

    Handling Multi-Database Connections

    3 min
  • 14

    Eloquent Caching Strategies

    3 min
  • 15

    Queue Worker Prioritization

    4 min
  • 16

    Unique Job Patterns

    4 min
  • 17

    Rate Limiting Background Jobs

    3 min
  • 18

    Event-Driven Architecture

    4 min
  • 19

    Integrating External Message Brokers

    4 min
  • 20

    Distributed Transactions and Sagas

    3 min
  • 21

    Eventual Consistency Patterns

    4 min
  • 22

    Multi-Layered Caching Strategy

    4 min
  • 23

    Cache Tagging and Invalidation

    4 min
  • 24

    Session Persistence in Clusters

    4 min
  • 25

    High-Availability Infrastructure

    4 min
  • 26

    Zero-Downtime Deployment Pipelines

    4 min
  • 27

    Advanced OAuth2 Implementation

    3 min
  • 28

    JWT and Stateless Security

    4 min
  • 29

    Multi-Tenant Security Isolation

    3 min
  • 30

    Defense Against SSRF

    3 min
  • 31

    Mass Assignment Hardening

    4 min
  • 32

    Automated Security Testing

    3 min
  • 33

    Custom Telemetry Design

    3 min
  • 34

    Distributed Tracing

    4 min
  • 35

    Profiling PHP Execution

    3 min
  • 36

    Memory Management in Long-Running Processes

    4 min
  • 37

    Testing DDD Components

    3 min
  • 38

    Contract Testing

    3 min
  • 39

    Handling Large File Uploads

    3 min
  • 40

    Optimizing Asset Pipelines

    4 min
  • 41

    Database Query Caching Layers

    3 min
  • 42

    Advanced Eloquent Scopes

    4 min
  • 43

    Distributed Locks

    3 min
  • 44

    API Versioning Strategies

    4 min
  • 45

    Database Migration Strategies

    4 min
  • 46

    Handling Webhooks Securely

    3 min
  • 47

    Advanced Logging Patterns

    3 min
  • 48

    Database Indexing for Joins

    4 min
  • 49

    Graceful Degradation

    3 min
  • 50

    Custom Middleware Development

    Coming soon
  • 51

    Database Connection Pooling

    Coming soon
  • 52

    Handling Large Data Exports

    Coming soon
  • 53

    Security Header Configuration

    Coming soon
  • 54

    Database Sharding Concepts

    Coming soon
  • 55

    Real-time Data Synchronization

    Coming soon
  • 56

    Database Deadlock Prevention

    Coming soon
  • 57

    Managing Third-Party API Integrations

    Coming soon
  • View full course