Un recurso, un controlador y sus rutas: con tres piezas ya tienes un endpoint que filtra, ordena, pagina y carga relaciones.
Por defecto el paquete actúa sobre las rutas que empiezan con api (mode = prefix, path_prefix = api). Todo lo demás responde como siempre. Para que toda la aplicación responda en JSON:API, usa JSON_API_MODE=global en el .env.
1. Un recurso
Un recurso dice qué se muestra de un modelo:
use Illuminate\Http\Request;
use MundoProgra\JsonApi\Resources\Relationship;
use MundoProgra\JsonApi\Resources\ResourceObject;
class ArticleResource extends ResourceObject {
// El nombre corto de la ruta que muestra un artículo: de ahí sale su `links.self`.
protected function jsonApiRoute(): ?string {
return 'articles.show';
}
// Los nombres se escriben en snake_case y salen en el estilo configurado (`createdAt` por defecto).
protected function jsonApiAttributes(Request $request): array {
return [
'title' => $this->title,
'status' => $this->status,
'created_at' => $this->created_at?->toIso8601String(),
];
}
// Solo salen las relaciones que están cargadas: el paquete nunca dispara una consulta para completar un documento.
protected function jsonApiRelationships(Request $request): array {
return [
'author' => $this->whenLoaded('author', fn() => Relationship::one($this->author, AuthorResource::class)),
'tags' => $this->whenLoaded('tags', fn() => Relationship::many($this->tags, TagResource::class)),
];
}
}
El type de un recurso es la tabla del modelo y su id es la clave de ruta, siempre como texto. Se cambian sobrescribiendo type() e id().
2. Un controlador
El controlador aplica la consulta del cliente con listas blancas que escribes tú:
class ArticleController {
public function index() {
return ArticleResource::collection(
Article::query()
->jsonApiFilter(exact: ['status', 'author' => 'author_id'], partial: ['title'], scopes: ['published'])
->jsonApiSort(['id', 'title', 'created_at'], default: '-id')
->jsonApiInclude(['author', 'tags', 'comments.author'])
->jsonApiPaginate()
);
}
public function show(string $article) {
return ArticleResource::make(
Article::query()->jsonApiInclude(['author', 'tags'])->findOrFail($article)
);
}
}
3. Las rutas
Las rutas son las de siempre; el paquete no registra ninguna. Solo hace falta que exista la que nombraste en jsonApiRoute(), porque de ella sale el links.self de cada recurso:
use Illuminate\Support\Facades\Route;
Route::get('articles', [ArticleController::class, 'index'])->name('articles.index');
Route::get('articles/{article}', [ArticleController::class, 'show'])->name('articles.show');
Si tus rutas llevan un prefijo en el nombre (por ejemplo v1.), indícalo en links., como se explica en Configuración.
Qué obtienes
Con eso, esta petición ya funciona de punta a punta:
GET /api/articles?filter[status]=published&sort=-createdAt&include=author&fields[articles]=title,author&page[size]=5
Filtra, ordena, pagina, carga el autor sin consultas de más y devuelve el documento con data, included, links y meta. Cada filtro, orden o include que no esté en tu lista responde 400 con el parámetro que falla.
Los detalles de cada parte están en Recursos, colecciones y relaciones y en Parámetros de consulta.