Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Lapiv Laravel Package

juliomotol/lapiv

Lapiv is a Laravel API versioning package that helps you organize versioned routes and controllers, resolve API versions from requests, and ship breaking changes without breaking existing clients. Simple configuration keeps evolving APIs clean and maintainable.

View on GitHub
Deep Wiki
Context7

Getting Started

lapiv adds API versioning to Laravel's router without forcing /v1/, /v2/ URL prefixes. Versions are declared once as an enum, and routes are registered under a version; the package resolves which version the incoming request wants and only those routes are considered.

  1. Install and publish the config:

    composer require juliomotol/lapiv
    php artisan vendor:publish --tag=lapiv-config
    

    This gives you config/lapiv.php — the only file you should normally need to touch (resolvers, default version, header/query key names).

  2. Create the version enum (the package ships a generator; otherwise hand-write a string-backed enum in app/ApiVersion.php):

    enum ApiVersion: string {
        case v1 = 'v1';
        case v2 = 'v2';
    }
    

    Look here first when something version-related misbehaves — casing of the enum values is what clients must send.

  3. Wrap routes in the apiVersion macro, either inline or around a per-version route file:

    Route::apiVersion(ApiVersion::v1)->group(base_path('routes/api/v1.php'));
    Route::apiVersion(ApiVersion::v2)->group(base_path('routes/api/v2.php'));
    
  4. Laravel 11 note (v3.2.0): with the slim skeleton there is no RouteServiceProvider. Register the versioned route files inside bootstrap/app.php via ->withRouting(using: ...) (or a small service provider) and call $middleware config as usual. Package discovery handles the service provider; nothing else changes.

First use case: you have a live /api/users endpoint and need to ship a breaking change. Leave v1 routes untouched, copy the file to v2, register it under ApiVersion::v2, and let the resolver pick. Clients that send nothing keep hitting v1 (via default_version), so the change is opt-in.

Implementation Patterns

One route file per version. Treat routes/api/v1.php, routes/api/v2.php as frozen contracts — never edit a shipped version file, only add to the newest one. This makes diffs reviewable and deprecation mechanical.

Resolution strategy is configuration, not code. The package's resolvers cover the usual suspects: query string (?version=v2), a custom header (e.g. X-Api-Version), and Accept-header content negotiation. Enable/order them in config/lapiv.php; the first resolver to return a value wins. For public APIs, lead with Accept (application/vnd.yourapp.v2+json); for internal/JS clients, the header is easier to set globally in an HTTP client.

Custom resolver. When none of the built-ins fit (subdomain, tenant setting, JWT claim), implement the package's resolver contract — a single method that takes the Request and returns an ApiVersion or null — and append the class to the resolvers array. Keep it pure and side-effect free; it runs early and often.

Reject unsupported versions explicitly. A resolver returning null usually falls back to the default version, which can silently serve old data to a client asking for v9. Add a middleware that checks the requested version against the enum and returns 400/406 (or 426 Upgrade Required with a Sunset header for retired versions).

Never couple controllers across versions. App\Http\Controllers\Api\V1\UserController and ...\V2\UserController should be independent even when 90% identical. Share behaviour via actions/services, not inheritance from a V1 base class — the moment v1 must not change, inheritance becomes a liability.

Route names must be version-prefixed. ->name('v1.users.index'), ->name('v2.users.index'). Unprefixed names collide because both versions register a users.index, and route('users.index') will resolve to whichever registered last.

Testing. Feature tests just set the header or query param:

$this->withHeader('Accept', 'application/vnd.yourapp.v2+json')
     ->getJson('/api/users');

Add one test per version per endpoint, plus a test asserting an unknown version is rejected — that contract is otherwise invisible until a client complains.

Deprecation workflow. Keep the old version registered, add a Sunset/Deprecation response header via middleware, log usage keyed by version, and remove the route file only when traffic metrics are zero.

Gotchas and Tips

  • Casing and exact values matter. v1 vs 1 vs V1 — clients send strings, enum values are your contract. Document the literal format; don't accept both unless a resolver normalises it.
  • Route caching. Because version-scoped routes are registered conditionally during boot, php artisan route:cache can behave differently from a fresh boot. After changing config/lapiv.php, enum cases, or route files, run route:clear and config:clear — a stale cached config is the most common "the package is broken" cause.
  • Middleware ordering. Version resolution has to happen before anything that branches on version (throttling buckets, auth guards, response transformers). If you bind the resolved version into the container, ensure that ordering in the api middleware group.
  • Octane / long-running workers. If the resolved version is cached statically anywhere, reset it per request (the package's own service provider handles this, but custom resolvers or your own cached accessor may not). Test under Octane before deploying.
  • 404 vs 400 ambiguity. An unmatched version often surfaces as a route 404 rather than a version error, which sends clients debugging the wrong thing. Wrap with the rejecting middleware so the failure is unambiguous.
  • Don't mix URL-prefix versioning with header versioning. Pick one. Running /api/v1/... and Accept: ...v2 produces routes that can never match and very confusing logs.
  • Extension points to reach for first: a custom resolver when your version source is non-standard, and a response middleware when you need Sunset/Deprecation signalling. Both are small and leave your controllers untouched.
  • Debugging tip: dump the resolved version early (a temporary middleware logging ApiVersion + the matched route name) — nearly every issue is a resolver returning null and the default silently taking over.
  • Laravel 11 specifics: the package now supports 11.x, but if you migrated from 10, double-check that your old RouteServiceProvider logic (including Route::apiVersion() calls) actually moved into bootstrap/app.php — a silently dropped registration shows up as a 404 on every version.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core