spatie/laravel-endpoint-resources
Abandoned package that adds controller/action-based URL links to Laravel API resources and collection meta. Includes traits to generate “show/edit/update/delete” item links and “index/create/store” collection links automatically.
Installation:
composer require spatie/laravel-endpoint-resources
Publish the config (if needed):
php artisan vendor:publish --provider="Spatie\ResourceLinks\ResourceLinksServiceProvider"
Basic Usage:
Extend your JsonResource with the HasLinks trait:
use Spatie\ResourceLinks\HasLinks;
class UserResource extends JsonResource
{
use HasLinks;
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'links' => $this->links(), // Auto-generates links
];
}
}
First Use Case:
Automatically generate API links for a User resource in a show endpoint:
public function show(User $user)
{
return new UserResource($user);
// Returns: { "id": 1, "name": "John", "links": { "self": "/users/1", "edit": "/users/1/edit", ... } }
}
Default Controller Mapping:
The package auto-detects RESTful controller actions (e.g., index, show, store, update, destroy) and generates links like:
$this->links(); // Returns: { "self": "/users/1", "edit": "/users/1/edit", ... }
Custom Controller Actions:
Define explicit actions in your ResourceLinks config or via the trait:
use Spatie\ResourceLinks\HasLinks;
class UserResource extends JsonResource
{
use HasLinks;
public function getLinks()
{
return [
'custom-action' => route('users.custom', $this->id),
];
}
}
Nested Resource Links:
For nested resources (e.g., Post under User), use the nested method:
$this->links()->nested('posts'); // Generates: { "posts": "/users/1/posts" }
Conditional Links: Dynamically include/exclude links based on user roles or resource state:
public function toArray($request): array
{
return [
'links' => $this->links()->onlyIf(
fn () => $request->user()->can('edit-users')
),
];
}
API Versioning: Leverage Laravel’s route model binding with versioned routes:
Route::apiResource('v1/users', UserController::class);
// Links will auto-respect the `v1` prefix.
API Documentation: Use the generated links in OpenAPI/Swagger docs to auto-populate endpoints:
paths:
/users/{id}:
get:
responses:
200:
$ref: '#/components/responses/User'
components:
responses:
User:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
properties:
links:
$ref: '#/components/schemas/Links'
Frontend Integration: Pass links to Vue/React for dynamic navigation:
// Example: Fetch user data and use `links.self` for redirects.
const { data: user } = await api.get(`/users/${id}`);
router.push(user.links.self);
Admin Panels: Integrate with Laravel Nova or Filament for auto-generated CRUD links:
// Nova Tool
public function fields(Request $request)
{
return array_merge(parent::fields($request), [
Link::make('Edit', $this->resource->links()->edit),
]);
}
GraphQL Wrappers: Expose links in GraphQL responses via Laravel GraphQL:
type User {
id: ID!
name: String!
links: Links!
}
Route Caching: Cache routes to improve link generation performance:
Route::cache();
// Run `php artisan route:cache` after changes.
Route Model Binding: Ensure your routes use implicit binding for consistency:
Route::apiResource('users', UserController::class)->except(['create', 'edit']);
Testing: Mock links in unit tests:
$resource = new UserResource(new User());
$resource->shouldReceive('links')->andReturn(['self' => '/mock-url']);
API Rate Limiting:
Apply rate limits to link endpoints (e.g., edit routes) separately:
Route::middleware('throttle:60,1')->group(function () {
Route::patch('/users/{user}', [UserController::class, 'update']);
});
Abandoned Package:
spatie/laravel-api-resource-links (updated fork).route() helper.spatie/laravel-query-builder for nested resource links.Route Name Conflicts:
users.edit and posts.edit), links may resolve incorrectly. Use absolute URLs or unique route names:
Route::name('users.edit')->patch('/users/{user}');
Route::name('posts.edit')->patch('/posts/{post}');
Missing Controller Actions:
approve), define them explicitly:
protected function getLinks()
{
return [
'approve' => route('users.approve', $this->id),
];
}
Circular References:
// UserResource links to PostResource, which links back to UserResource.
// Solution: Use `only()` to limit links.
$this->links()->only(['self', 'edit']);
Localization Issues:
users.{locale}) may break link generation. Use absolute paths or static route names:
Route::name('users.edit')->patch('/users/{user}/edit');
Inspect Generated Links: Dump links to debug resolution:
dd($this->links()->toArray());
Route Debugging: List all routes to verify names:
php artisan route:list
Missing Routes:
If a link returns null, check:
php artisan route:list).{user} vs {id}).Default Excludes:
The package excludes create and store links by default for show resources. Override in config:
'excluded_actions' => [
'create',
// 'store', // Uncomment to include.
],
Custom Link Methods: Extend the trait to add methods:
class UserResource extends JsonResource
{
use HasLinks;
public function links()
{
return parent::links()->merge([
'profile' => route('users.profile', $this->id),
]);
}
}
Query Parameters: Preserve query params in links (e.g., for pagination):
$this->links()->withQueryParams(['page' => 2]);
Custom Link Providers: Create a service provider to add global link logic:
class CustomLinkProvider
{
public function __invoke($resource)
{
return [
'webhook' => route('users.webhook', $resource->id),
];
}
}
Register in AppServiceProvider:
ResourceLinks::extend('webhook', CustomLinkProvider::class);
Dynamic Link Resolution: Use closures for runtime link generation:
$this->links()->merge([
'download' => function () {
return route('users.download', $this->id, [
'format' => 'pdf',
]);
},
]);
Link Formatting: Modify link output (e.g., add API version):
public function toArray($request
How can I help you explore Laravel packages today?