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

Hateoas Bundle Laravel Package

fsc/hateoas-bundle

Symfony bundle that adds HATEOAS hypermedia links to your API responses. Define relations and links with annotations, integrate with JMS Serializer and Symfony routing, and expose discoverable HAL/JSON-LD resources so clients can navigate your API at runtime.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check: fsc/hateoas-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, and no config/hateoas.php. Its composer constraints target Symfony 2.x/3.x and an era of PHP that predates Laravel 8. On a modern Laravel app composer require fsc/hateoas-bundle will either fail dependency resolution or drag in a symfony/framework-bundle you cannot boot. Treat this package as a reference implementation and pattern source, not a drop-in dependency.

If you are in a legacy Symfony service (the only place it actually runs):

  1. composer require fsc/hateoas-bundle alongside jms/serializer-bundle — the bundle hooks the JMS serialization pipeline, not Symfony's normalizer.
  2. Register new FSC\HateoasBundle\FSCHateoasBundle() in AppKernel.
  3. Add a fsc_hateoas block to config.yml (metadata cache, links key) and ensure your API routes are named.
  4. Annotate a controller action with a Relation and serialize the entity through JMS; the response gains a _links (or configured) key.

Where to look first: the README is effectively the manual — there is no docs site. Then Annotation/ (Relation, Embedded, Exclusion), Serializer/Handler/ (where links are injected into the JSON), and Resources/config/ for the service ids you would decorate.

First use case: a GET /users/{id} endpoint that returns a user plus a self link:

/**
 * @Relation("self", route="api_user_show", routeParameters={"id" = "id"})
 */
public function getUserAction($id) { /* return User */ }

Laravel equivalent to build instead: a UserResource extends JsonResource returning ->additional(['_links' => ['self' => route('api.users.show', $this->id)]]). Same contract, no bundle.

Implementation Patterns

Route-driven link generation. The bundle's core idea is that links are declared next to the action that serves them, using the route name plus an expression over request/entity data. This keeps URLs out of your entities and guarantees self links match real routes. The Laravel analogue is generating links from named routes inside JsonResource::toArray()/additional() rather than hardcoding paths.

Embedding sub-resources. @Embedded inlines related representations (e.g. a user's friends) so clients avoid extra round-trips; @Exclusion prunes fields. Combined with JMS serialization groups this is how you control payload shape per endpoint. In Laravel this maps to whenLoaded() / whenIncluded() plus explicit Relation::load() guards.

Programmatic links. Annotations are only sugar — the LinkFactory / LinkManager services can build Link objects in plain service code (e.g. a domain service producing a next link for a cursor). This is the extension point worth copying: a single LinkBuilder class in Laravel that everything funnels through, so relation names and URL shapes stay consistent.

Pagination links. The Pagerfanta integration emits first/last/next/prev for embedded collections — the pattern to replicate with Laravel's LengthAwarePaginator and $paginator->nextPageUrl().

A workable Laravel port:

abstract class HateoasResource extends JsonResource
{
    protected array $links = [];

    public function link(string $rel, string $url): static
    {
        $this->links[$rel] = ['href' => $url];
        return $this;
    }

    public function toArray($request): array
    {
        return parent::toArray($request);
    }

    public function with($request): array
    {
        return ['_links' => $this->links];
    }
}

Consistency beats cleverness. Whatever you build, keep rel values a closed set (self, collection, next, prev, related) documented in one place. Leaking internal route names as rel values is the single most common way this kind of layer rots.

Gotchas and Tips

  • It is effectively unmaintained. Last meaningful activity is pre-Symfony 4. No PHP 8 attributes, no Symfony Flex recipe, no Laravel anything. Forking is a real cost; assume you will own the serialization handler.
  • Links are resolved at serialization time, so any bug surfaces as a malformed JSON body, not an exception in your controller. Dump the serialized output in a functional test, not just the controller return value.
  • Metadata is parsed and cached. Annotations are read reflectively; in prod you rely on the fsc_hateoas metadata cache. Changing an annotation without warming/clearing that cache silently serves stale links — the Symfony equivalent of a Laravel config-cache surprise.
  • Route renames break payloads silently. route="api_user_show" becoming api_users_show produces a 500 at serialization, not at boot. Add assertions on _links keys in tests; this applies equally to Laravel's route() helper.
  • Proxy and scheme handling. Link generation uses the request context. Behind a load balancer without trusted proxies you get http:// links in an https:// API. Configure trusted proxies/URL::forceScheme and test the absolute URL, not just the path.
  • Embedding explodes payloads and queries. @Embedded chains are easy to declare and expensive to serve; N+1 creeps in through the JMS handler where your query log won't obviously point. Bound embedding with serialization groups and measure response size.
  • Expression language in annotations is powerful and opaque. Evaluating services or entity getters inside href/routeParameters makes failures hard to trace. Keep expressions to simple property reads; move logic into the LinkFactory.
  • Response shape is config-driven but a client contract. The links key and nesting come from fsc_hateoas config — pin it, document it, and version it. Clients break when a config tweak reflows the envelope.
  • Don't force HAL or JSON:API out of it. If you need application/hal+json or JSON:API semantics, use a spec-compliant library (in Laravel: laravel-graphql-style dedicated packages, spatie/laravel-fractal, or LaravelJsonApi) — the bundle predates both specs' popular tooling.
  • Extension point worth stealing: decorate the link factory rather than editing annotations. A single seam where every href is produced makes logging, absolute-URL forcing, and testing trivial.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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