Skip to content
MundoProgra JSON:API
Menu

Start

Getting started

A resource, a controller and its routes: with three pieces you already have an endpoint that filters, sorts, paginates and loads relationships.

By default the package acts on routes starting with api (mode = prefix, path_prefix = api). Everything else answers as usual. To make the whole application respond in JSON:API, set JSON_API_MODE=global in .env.

1. A resource

A resource says what is shown of a model:

use Illuminate\Http\Request;
use MundoProgra\JsonApi\Resources\Relationship;
use MundoProgra\JsonApi\Resources\ResourceObject;

class ArticleResource extends ResourceObject {
  // The short name of the route that shows an article: its `links.self` comes from there.
  protected function jsonApiRoute(): ?string {
    return 'articles.show';
  }

  // Names are written in snake_case and come out in the configured style (`createdAt` by default).
  protected function jsonApiAttributes(Request $request): array {
    return [
      'title' => $this->title,
      'status' => $this->status,
      'created_at' => $this->created_at?->toIso8601String(),
    ];
  }

  // Only loaded relationships come out: the package never fires a query to complete a document.
  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)),
    ];
  }
}

The type of a resource is the model table and its id is the route key, always as text. Override type() and id() to change them.

2. A controller

The controller applies the client query with whitelists you write:

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. The routes

The routes are the usual ones; the package registers none. The only requirement is that the one you named in jsonApiRoute() exists, because each resource's links.self comes from it:

use Illuminate\Support\Facades\Route;

Route::get('articles', [ArticleController::class, 'index'])->name('articles.index');
Route::get('articles/{article}', [ArticleController::class, 'show'])->name('articles.show');

If your routes carry a prefix in their name (for example v1.articles.show), set it in links.route_name_prefix, as explained in Configuration.

What you get

With that, this request already works end to end:

GET /api/articles?filter[status]=published&sort=-createdAt&include=author&fields[articles]=title,author&page[size]=5

It filters, sorts, paginates, loads the author without extra queries and returns the document with data, included, links and meta. Every filter, sort or include that is not in your list answers 400 naming the failing parameter.

The details of each part are in Resources, collections and relationships and in Query parameters.