gointegro/hateoas
Laravel package for building HATEOAS-compliant REST APIs. It adds hypermedia links and embedded relationships to Eloquent resources, supports HAL and JSON:API formats, and helps you ship self-descriptive, discoverable APIs with minimal boilerplate.
gointegro/hateoas is a Laravel 4/5-era package for exposing Eloquent models as HATEOAS/JSON API resources. Treat it as a legacy integration: the last release was in 2015, so the first task is proving it installs and boots against your Laravel version before designing around it.
Minimal path:
Install it:
composer require gointegro/hateoas
Register the service provider manually. The package predates Laravel package auto-discovery, so add it to config/app.php:
'providers' => [
// ...
GoIntegro\Hateoas\HateoasServiceProvider::class,
],
Publish and inspect the config:
php artisan vendor:publish --provider="GoIntegro\Hateoas\HateoasServiceProvider"
On older Laravel 4 installations this may be php artisan config:publish gointegro/hateoas.
Look first at:
config/hateoas.php — resource mapping, media type/serializer, pagination settings.src/GoIntegro/Hateoas/Resources/Resource.php — base resource API, link helpers, and how the wrapped model is exposed.ResourceCollection — collection/pagination behavior.Wire one model end-to-end. A typical first resource:
namespace App\Resources;
use GoIntegro\Hateoas\Resources\Resource;
class PostResource extends Resource
{
public function getLinks()
{
$post = $this->resource; // accessor may differ by version; check the base class
return [
new Link('self', route('posts.show', $post->id)),
new Link('author', route('users.show', $post->user_id)),
];
}
}
Register the model-to-resource mapping, if your version requires it:
// config/hateoas.php
return [
'resources' => [
App\Post::class => App\Resources\PostResource::class,
],
];
Return it from a controller:
public function show($id)
{
return new PostResource(Post::findOrFail($id));
}
The first useful milestone is a GET /posts/{id} response containing data, type, id, attributes, and a links object with a working self URL. Once that works, add a collection and one relationship.
One resource class per model, with a shared base resource. Keep link and serialization logic out of Eloquent models. Use a base class for common links such as self, and override or extend for model-specific links:
abstract class BaseResource extends Resource
{
protected function selfLink()
{
return new Link('self', route($this->routeBaseName().'.show', $this->resource->id));
}
}
Authorization-aware links. In HATEOAS, the presence of a link is an API contract. Do not emit edit or delete links unless the current user can perform those actions:
public function getLinks()
{
$links = [new Link('self', route('posts.show', $this->resource->id))];
if (Gate::allows('update', $this->resource)) {
$links[] = new Link('edit', route('posts.edit', $this->resource->id));
}
return $links;
}
This keeps the API honest and prevents clients from discovering actions they cannot use.
Collections and pagination. Use the package's collection class rather than returning raw arrays. Pagination links (first, prev, next, last) should be generated by the same URL layer as self:
public function index()
{
return new PostCollection(Post::query()->paginate(25));
}
If your version supports a paginator adapter, use it so page metadata and links stay consistent with the configured media type.
Relationship inclusion and N+1 avoidance. HATEOAS clients often request embedded resources via ?include=author,comments. Parse that parameter in the controller or a query object, eager-load exactly those relations, and let the resource layer serialize them. Do not lazily walk relations inside getLinks() or relationship methods without eager loading, or a collection endpoint will produce an N+1 storm.
Write endpoints. If your version includes a hydrator, use it to translate JSON API documents into model attributes, but validate before hydration and persist inside a transaction. If the hydrator is incomplete or version-specific, keep a thin mapper in the controller and use the resource only for output:
$payload = $request->json()->all();
$validator = Validator::make($payload['data']['attributes'] ?? [], [
'title' => 'required|string|max:255',
]);
$validator->validate();
$post = Post::create($payload['data']['attributes']);
return new PostResource($post);
Content negotiation and middleware. Register any package middleware in the correct order, especially if it handles Accept headers or JSON API media types. A request with the wrong Accept header may produce an empty body or a 406 rather than a helpful error. Test with the exact media type your config advertises:
curl -H "Accept: application/vnd.api+json" http://localhost/posts/1
Testing pattern. Assert structure, not exact URLs, and assert link presence separately:
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => [
'type',
'id',
'attributes' => ['title', 'body'],
'links' => ['self'],
],
]);
Use URL::forceRootUrl() in CLI tests if link generation depends on the request host.
Keep resources presentation-only. Resources should compose links and expose attributes; business rules belong in policies, services, or query objects. This makes the API layer replaceable if you later migrate off the package.
php artisan config:clear (and config:cache again if used). A stale cached config is a common cause of "my new resource is ignored."Resource.php rather than guessing between $this->resource, $this->entity, and $this->getResource(). Copy the pattern from the package tests.route() produces absolute URLs based on the current request. In queue workers, console commands, or tests without a request, set URL::forceRootUrl() and URL::forceScheme() explicitly, or links will point at http://localhost.Post -> author -> posts -> author can recurse indefinitely if relationships are serialized eagerly. Use explicit includes, depth limits, or a serializer that only embeds requested relationships.http instead of https or use an internal hostname. Fix at the URL generator/proxy layer, not by post-processing strings.App\Exceptions\Handler and convert to the same media type and error document shape as successful responses.Resource and ResourceCollection, override link generation, bind a custom serializer/hydrator if the package exposes container bindings, and keep custom behavior in your application namespace rather than editing vendor code.How can I help you explore Laravel packages today?