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

Rest Server Laravel Package

innmind/rest-server

Framework-agnostic REST server for PHP that exposes domain resources over HTTP with configurable routes, CRUD-style operations, content negotiation, and consistent error handling. Built on Innmind’s HTTP abstractions for embedding APIs into existing applications.

View on GitHub
Deep Wiki
Context7

Getting Started

innmind/rest-server is not a Laravel package — it's a standalone, framework-agnostic REST layer built on PSR-7 (innmind/http), immutable collections (innmind/immutable), and a functional/DI toolkit (innmind/mantle). There is no service provider, no facade, and no Eloquent integration. You are wiring it into Laravel yourself.

Minimum steps:

  1. Vendor it deliberately. The repo is archived. Check the composer.json PHP constraint against your app (it targets PHP 7-era) and run composer why-not innmind/rest-server before installing, because its PSR-7 and innmind/* dependencies frequently collide with modern Laravel stacks. If it won't resolve, fork it, loosen the constraints, and add "replace" to your fork's composer.json.
  2. Read in this order: src/Server.php → src/RequestHandler.php → src/Definition/HttpResource.php (the resource/field/access DSL) → src/Gateway.php (the storage contract) → tests/. The tests are the only real documentation; the README is thin.
  3. Bridge it into a route. The package consumes and produces PSR-7 messages, so you need a translation layer:
// routes/api.php
Route::any('/v1/{path?}', ApiBridgeController::class)->where('path', '.*');

// ApiBridgeController
public function __invoke(Request $request)
{
    $psr7 = (new HttpFoundationFactory)->createRequest($request);
    $response = $this->restServer->handle($psr7);   // returns PSR-7 ResponseInterface
    return (new PsrHttpFactory(...))->createResponse($response);
}

Use symfony/psr-http-message-bridge plus nyholm/psr7 (or guzzlehttp/psr7) for both directions.

  1. First working use case: one read-only resource. Define an HttpResource with a handful of typed fields and read-only access, implement the Gateway interface against one Eloquent model (read() + search()), and expose GET /api/v1/things and GET /api/v1/things/{id}. Get that green in a feature test before adding writes — writes drag in validation, ranges, and status-code mapping, which is where most of the friction lives.
// Shape only — the exact builder is version-specific, confirm against src/Definition/.
$users = HttpResource::builder('user', '/users/{id}')
    ->fields([
        Field::of('id',   Type\IntType::of())->access(Access::readOnly()),
        Field::of('name', Type\StringType::of())->access(Access::readWrite()),
    ])
    ->build();

Implementation Patterns

Gateway-per-aggregate, mapped to repositories. The Gateway interface (create / read / update / remove / search) is your anti-corruption boundary. One implementation per Eloquent model, each translating the package's criteria objects into a query builder call. Keep these tiny and heavily tested; they're the only place the domain and the package meet.

Definitions as configuration, not code. Don't scatter HttpResource::builder() calls across the app. Build them in a single ApiDefinitionProvider from a config array (or a PHP attribute scan you write yourself), keyed by API version. Adding a field should be a config edit, not a code hunt.

Never leak definition objects. Innmind\Rest\Server\Definition\* objects, Range, and Link should not appear in your controllers, jobs, resources, or domain services. Convert at the gateway and at the response boundary. If you later throw the package away, an anti-corruption layer keeps the blast radius to two files.

Auth and tenancy go in the bridge, not the package. There is no authentication, no rate limiting, no CORS, and no authorization in this library. Put Laravel middleware on the route and enforce scoping again inside each gateway (->where('tenant_id', $tenantId)) — the bridge is one refactor away from being bypassed.

Compose around the Server instance rather than forking it: decorators that log, time, enrich _links, or short-circuit on maintenance mode. Because everything is immutable, decorators are clean — you get a new object back each time.

Search translation is the real work. Map the package's Range/criteria semantics to Laravel's page/per_page (and filter[...]) so clients get familiar pagination. Always impose a hard max page size in the gateway; the library won't do it for you.

Testing strategy. Unit-test gateways against the criteria objects directly. Feature-test the full stack through Laravel's HTTP kernel so the PSR-7 bridge is covered — the bridge is where encoding, headers, and status codes actually break.

Versioning. Mount each version's definition set under a distinct prefix (/api/v1, /api/v2) with its own Server. Definitions are cheap to duplicate; branching inside one set is not.

Migration path. Because the HTTP surface is defined declaratively, you can freeze the contract (routes, field names, status codes) in feature tests and later swap the implementation for hand-written Laravel controllers. Write those contract tests from day one.

Gotchas and Tips

It is archived — treat it as vendored code. No security patches, no PHP 8 compatibility work, no upstream fixes. Pin the exact version, keep a fork ready, and don't plan on composer update being uneventful. If this is production, budget for the day you replace it.

Everything is immutable. Methods return new instances. A missed assignment ($resource = $resource->withX(...)) silently does nothing — no exception, no warning, just a field that never appears. This is the single most common bug with innmind/* libraries. Enable a static analyser (Psalm/PHPStan) at a high level to catch discarded returns.

No error shaping. Exceptions that escape the request handler become bare 500s. Register your own try/catch around Server::handle() and map to Laravel's JSON error envelope — otherwise you'll break client error handling the first time a gateway throws.

Base URL and proxies. Generated _links and Location headers on 201 are built from the request URI. Behind a load balancer, configure TrustProxies and ensure the scheme/host survive the HttpFoundation → PSR-7 conversion, or you'll ship http:// links in production.

PSR-7 is not Illuminate\Http\Request. No session, no CSRF token, no $request->user(), no validation, no file upload objects, no request macros. Anything you rely on must be resolved before the bridge and passed in via attributes or a container-bound context object.

Content negotiation is your job. The package will parse Accept, but which formats you support and what 406 looks like is a decision you implement. Explicitly allow-list application/json and reject everything else up front — debugging a half-negotiated response is miserable.

Debug at the boundary. You can't easily step through the internals. Log the raw PSR-7 request (method, path, headers, body) and the raw response before conversion. That one log line answers 90% of "why is this 404/405/415" questions.

Extension points, in order of usefulness: Gateway implementations (storage), the request/response bridge (headers, auth context), decorators around Server (cross-cutting behaviour), then custom formats/serializers (rare — only if you need HAL, JSON:API, or Siren output). Reach for those before touching vendor code.

Performance traps. search() is called freely by the library; always paginate and always select() only the defined fields. Link expansion is not lazy — an aggressive Link definition can turn one request into N+1 queries. Set a hard cap on expansion depth.

Don't fight the immutability for config. Inject a fully-built Server/definition set from the container at boot. Rebuilding definitions per request is wasteful and makes tests slower than they need to be.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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