php 118 lines · 4 tabs

Versioning a Laravel API with Route Groups and Resource Transformers

Shared by codesnips Sep 2026
4 tabs
<?php

namespace App\Http\Resources\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class ArticleCollection extends ResourceCollection
{
    public $collects = ArticleResource::class;

    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
        ];
    }

    public function with(Request $request): array
    {
        return [
            'meta' => [
                'version' => 'v1',
                'total' => $this->resource->total(),
            ],
            'links' => [
                'self' => $request->url(),
            ],
        ];
    }
}
4 files · php Explain with highlit

This snippet shows how to version a Laravel JSON API cleanly by combining a versioned route group with dedicated API Resource classes per version, so the wire format stays stable even as the underlying Article model evolves. The core idea is separation: the route group decides which version is being served, and the resource decides how the model is serialized for that version. Keeping both concerns explicit avoids the common trap of leaking new database columns into old clients.

In routes/api.php, all endpoints are nested under a prefix('v1') group with a name('api.v1.') prefix so route names never collide across versions. The group also attaches middleware like throttle:api and auth:sanctum in one place, which means a future v2 group can reuse the same controller or point to a V2 namespace without duplicating cross-cutting config. Grouping by prefix is preferred over header-based versioning here because it is trivially cacheable, easy to document, and visible in logs.

The ArticleController stays thin and version-agnostic in structure: index wraps a paginated query in an ArticleCollection, and show wraps a single model in an ArticleResource. Returning a resource instead of response()->json($model) is the key move — the controller never decides field names, so business logic and presentation don't blur together.

ArticleResource defines the exact v1 contract in its toArray method. Note how authorName is derived from a relationship and tags is coerced with whenLoaded to avoid N+1 surprises and to omit data the caller didn't request. The with method appends a stable meta.version envelope to every response, which is invaluable for client-side debugging. Casting published_at explicitly guards against timezone drift in the serialized output.

ArticleCollection sets $collects to bind each element to ArticleResource, then augments the top-level payload with meta.total and a links.self pointer. A subtle pitfall this addresses: when a collection wraps resources, per-item with data is ignored, so shared metadata belongs on the collection. When a v2 contract is needed, a developer copies these resource classes rather than editing them, guaranteeing old clients keep their guarantees.


Related snips

Share this code

Here's the card — post it anywhere.

Versioning a Laravel API with Route Groups and Resource Transformers — share card
Link copied