Skip to main content
Laravel, shipping fast.

Chapter 17

The Shared Package

Julian Beaujardin

Everything so far in this book has been true of one API. One EnsureTokenIsValidMiddleware. One ErrorResponse. One team that agreed, once, on how a controller should look. That agreement is easy to keep when there's a single codebase to keep it in.

It gets harder the moment there's a second service. A license API. A domain API. An AI-generation API. A customer-facing app that talks to all three. Each one needs the same bearer-token auth, the same rate limiting, the same { "data": {...} } envelope, the same retry-and-backoff behavior when a downstream call gets throttled. You can write that logic four times, or you can write it once and depend on it four times. This chapter is about the second option, and about the discipline it demands that writing the same code four times never asked of you.

A shared package is how a dozen services agree without a meeting.

One API becomes a fleet

Here's the shift this chapter marks. Every earlier chapter answered the question "how should this endpoint behave?" This chapter answers a different one: "how do a dozen services behave the same way, without a dozen people remembering to make them?"

One service validates a bearer token. Another service validates a bearer token. A third service validates a bearer token. Written independently, that's three implementations, three subtly different bugs waiting to diverge, and three places a fix has to land the next time someone finds a hole in how expiry is checked. Written once and depended on everywhere, it's one implementation, one bug surface, one place a fix lands.

The fleet behind this book solves this with webplo/api-infrastructure, a Composer package installed into every API app the same way any third-party dependency is installed. It isn't a service you deploy. It isn't a service you can curl. It's a library that gets compiled into api-server, api-license, and api-ai at build time, and its code runs inside each of those processes as if it had been written there. That distinction, library versus service, is the one this whole chapter turns on, and it's easy to blur if you're not paying attention.

What a shared package should own

Here's the rule: put a concern in the shared package when every consuming service must do it identically, and getting it wrong anywhere is a real problem, not a style preference.

Auth is the clearest case. Every current app registers the shared package's provider in bootstrap/providers.php instead of writing its own auth stack:

// bootstrap/providers.php
return [
    ApiInfrastructureProvider::class,
    ServerServiceProvider::class,
];

Two lines. One of them is the app's own thin provider, from Chapter 2. The other pulls in everything the fleet has agreed every API must do the same way. Nobody on this app's team wrote EnsureTokenIsValidMiddleware. Nobody needs to. It arrived with the dependency, the same way pagination arrived with Eloquent.

That single line buys a service five things it never has to re-decide:

  • Bearer-token authentication: EnsureTokenIsValidMiddleware resolves and caches the token, checks expiry, and stamps the resolved model onto the request as bearer, the same way in every app.
  • Rate limiting: a named limiter, keyed by token or falling back to IP, registered once in the provider's boot() method instead of copied into each app's own rate-limiting config.
  • Standard response and error shapes: BaseResponse and its ModelResponse / CollectionResponse / ErrorResponse subclasses from Chapter 2, so a client hitting three different services still parses one envelope shape.
  • A resilient outbound HTTP client: the SendsRequests trait, so a 429 from a downstream provider gets retried with backoff instead of failing the whole job, in every integration that uses it, not just the ones whose author remembered to write retry logic.
  • A health-check endpoint: registered from the package's own route file, so bootstrap/app.php's health: '/up' from Chapter 13 means the same thing everywhere it appears.

This is lesson one of this chapter, stated plainly: cross-cutting concerns should be an inherited structural guarantee, not a habit each service author has to remember. An inherited edge, every service authenticates the same way because the auth code physically lives in what they depend on, can't be skipped. A remembered edge, every service should authenticate the same way, and every developer knows to copy the pattern, gets skipped somewhere, eventually, by someone new, on a Friday, under deadline. You don't get to choose whether a service author forgets something. You only get to choose whether forgetting is possible.