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

Laravel Apiroute Laravel Package

grazulex/laravel-apiroute

Attribute-based API routing for Laravel: define endpoints directly on controllers, auto-register routes, and organize versioned APIs with middleware, resources, and validation support. Keeps route files clean, reduces boilerplate, and makes REST API code easier to maintain and scale.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and pin. composer require grazulex/laravel-apiroute — with ~111 stars this is a small, single-maintainer package, so commit the lockfile and read the changelog before minor bumps. It is MIT licensed, so vendoring/forking is a viable escape hatch if the maintainer goes quiet.
  2. Publish the config first. Run the package's publish command (php artisan vendor:publish --tag=apiroute-config — confirm the exact tag with php artisan vendor:publish and picking the provider interactively). The config file is the real API surface: it is where you declare versions and their lifecycle state (active / deprecated / sunset / retired). Read it top to bottom before writing any routes.
  3. Read the source, not just the README. At this size the package is small enough to skim in ~20 minutes. Look at the service provider (to see what route macros, middleware aliases, and bindings are registered) and the middleware (to see exactly which headers are emitted). This is faster than guessing from docs.
  4. Do not retrofit everything at once. Pick your most-trafficked API group — say /api/v1/orders — and register just that group under the package. Keep the rest of the app on plain Route::prefix('api/v1') until you have confirmed the header and routing behaviour in a staging environment.
  5. First use case: announce a deprecation. The highest-value day-one action is not version creation, it's telling existing clients that v1 is going away. Configure v1 as deprecated with a sunset date, hit an endpoint with curl -i, and verify the Deprecation / Sunset (RFC 8594) headers appear. If that works end-to-end, the package is earning its keep.

Implementation Patterns

One route file per version, registered through the package. The idiomatic layout is routes/api/v1.php, routes/api/v2.php, each wrapped by the package's version registration rather than hand-rolled Route::prefix() calls. This keeps the version→lifecycle mapping in one config file while the route definitions stay reviewable in diffs.

// routes/api.php
ApiRoute::version('v1', fn () => require __DIR__.'/api/v1.php');
ApiRoute::version('v2', fn () => require __DIR__.'/api/v2.php');

Deprecation as a scheduled state, not a code change. Treat the lifecycle state as configuration that flips over time. A practical workflow: active → deprecated (headers + changelog notice, minimum two release cycles) → sunset (hard 410 for anything still calling it) → retired (routes deregistered entirely). Put the flip dates in your calendar or, better, in a scheduled Artisan command/test that fails when a sunset date has passed but the state hasn't been updated.

Version detection: pick one strategy and enforce it. The package generally supports path-prefix and/or header-based (Accept: application/vnd.api.v2+json) resolution. Do not support both simultaneously — you get ambiguity in caching layers and confusing 404s. Path prefix is the pragmatic default for public APIs; header negotiation is worth it only if you have clients that genuinely need content negotiation.

Middleware ordering matters. Register the version/deprecation middleware outside your auth and throttle middleware so that deprecated-version responses still carry headers even when the request is rejected with 401/429. Test that specifically — it's the case clients actually hit.

Share logic, not routes. Version handlers should delegate to the same application services with different serializers/transformers. The pattern that ages well: v2 controllers extend v1 or call shared Actions, and only the Resource/transformer classes differ. Avoid duplicating business logic across version directories; duplication is the thing that makes sunsetting feel impossible.

Pair it with contract tests. For each version, assert (a) the version header is present, (b) the deprecation headers appear when configured, (c) the response shape matches a stored JSON fixture. Laravel's assertHeader covers the first two cheaply:

$this->getJson('/api/v1/orders')
    ->assertHeader('Sunset')
    ->assertOk();

Integration with docs generation. If you use Scribe or Scramble, tag generated docs per version and surface the sunset date in the docs page itself. A Sunset header nobody reads is less effective than a banner in the developer portal.

Gotchas and Tips

  • route:cache and config caching. Route registration that happens conditionally at runtime (e.g., registering a version only if its date has passed) will be frozen into the cached routes. Any lifecycle logic that depends on "today" must live in middleware, not in route registration, or you must clear the route cache on deploy.
  • Proxy and CDN header stripping. Deprecation and Sunset are easy for reverse proxies and CDNs to drop or cache incorrectly. If a v1 response is cached for 24h, the sunset notice arrives late. Add Cache-Control variation or purge on lifecycle flips, and verify headers with curl -i against production, not just phpunit.
  • Route name collisions across versions. Two versions both defining orders.show will have the last one win under route('orders.show'). Namespace names per version (v1.orders.show) or accept that URL generation is version-implicit.
  • Sunset dates are dates, not timestamps — and timezones bite. RFC 8594 expects an HTTP-date. Confirm whether the package formats with gmdate(); a local-timezone timestamp will render as a subtly wrong instant for clients doing strict comparison.
  • Version in the route vs. version in the payload. Some clients read meta.version from JSON; some read a header; some only look at the URL. Decide which is authoritative and document it. Emitting all three is fine; making them disagree is a support ticket generator.
  • Retiring too fast on internal consumers. Do a grep across your own first-party frontends and mobile apps for hardcoded /v1/ before flipping to sunset. Internal callers are the ones that break silently, because they don't read your deprecation headers.
  • Extension points worth knowing. The middleware is the natural place to swap in your own headers/vendor media types; the config's per-version array is where you'd add metadata like owning team or changelog URL if the package passes config through to headers. Check whether the package's version registry is bound in the container as a singleton — if so, you can resolve it in an admin controller to render a "current API versions" status page for free.
  • Debugging checklist when a version "doesn't work": clear bootstrap/cache/config.php and routes cache, confirm the middleware alias is registered (older publishes may reference a class name that changed), and dump Route::getRoutes() filtered by prefix to confirm the version actually registered before blaming the headers.
  • Bus factor. With a small star count and a single maintainer, wrap your usage behind your own thin layer (a VersionRegistry facade or your own middleware that reads the package config) so swapping the package out later is a one-file change rather than a codebase-wide edit.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin