This is a rough draft of a guide I'll flesh out after Laracon Online as I continue to develop the best pattern for this.
See a practical (incomplete) example of this on my "JSON-API Examples" GitHub repo.
laravel new myproject && cd myproject
php artisan make:model Article -mfs
php artisan make:model Comment -mfs
guarded = ['id']
to both modelsarticle() { return $this->belongsTo(Article::class); }
to Comment
author() { return $this->belongsTo(User::class, 'author_id'); }
to Comment
comments() { return $this->hasMany(Comment::class); }
to Article
author() { return $this->belongsTo(User::class, 'author_id'); }
to Article
comments() { return $this->hasMany(Comment::class); }
to User
article() { return $this->hasMany(Article::class); }
to User
$table->text('body');
$table->unsignedBigInteger('article_id');
$table->foreign('article_id')->references('id')->on('articles');
$table->unsignedBigInteger('author_id');
$table->foreign('author_id')->references('id')->on('users');
$table->string('title');
$table->text('body');
$table->unsignedBigInteger('author_id');
$table->foreign('author_id')->references('id')->on('users');
composer require spatie/laravel-fractal
composer require spatie/laravel-query-builder
php artisan make:transformer ArticleTransformer
php artisan make:transformer AuthorTransformer
php artisan make:transformer CommentTransformer
protected $availableIncludes = [
'author',
'comments',
];
public function transform(Article $article)
{
return [
'id' => (int) $article->id,
'title' => $article->title,
'created_at' => $article->created_at->format('c'),
];
}
public function includeAuthor(Article $article)
{
return $this->item($article->author, new AuthorTransformer, 'authors');
}
public function includeComments(Article $article)
{
return $this->collection($article->comments, new CommentTransformer, 'comments');
}
protected $availableIncludes = [
'author',
'article',
];
public function transform(Comment $comment)
{
return [
'id' => (int) $comment->id,
'body' => $comment->body,
];
}
public function includeAuthor(Comment $comment)
{
return $this->item($comment->author, new AuthorTransformer);
}
public function includeArticle(Comment $comment)
{
return $this->item($comment->article, new ArticleTransformer);
}
php artisan vendor:publish --provider="Spatie\Fractal\FractalServiceProvider"
config/fractal.php
and set serializer:
'default_serializer' => \League\Fractal\Serializer\JsonApiSerializer::class,
php artisan make:controller "Api\ArticleController"
php artisan make:controller "Api\ArticleCommentController"
routes/api.php
:
Route::get('articles', 'Api\ArticleController@index');
Route::get('articles/{article}', 'Api\ArticleController@show');
Route::get('articles/{article}/comments', 'Api\ArticleCommentController@index');
public function index()
{
$articles = QueryBuilder::for(Article::class)
->allowedIncludes(['author', 'comments'])
->allowedSorts(['created_at', 'title'])
->paginate();
return fractal($articles, new ArticleTransformer)->withResourceName('articles')->respond();
}
public function show($articleId)
{
$article = QueryBuilder::for(Article::class)
->allowedIncludes(['author', 'comments'])
->findOrFail($articleId);
return fractal($articles, new ArticleTransformer)->withResourceName('articles')->respond();
}
public function index($articleId)
{
$comments = QueryBuilder::for(Comment::class)
->allowedIncludes(['author', 'article'])
->where('article_id', $articleId)
->paginate();
return fractal($comments, new CommentTransformer)->withResourceName('comments')->respond();
}
// In a service provider:
Fractal::macro('respondJsonApi', function ($statusCode = 200) {
return $this->respond($statusCode, [
'Content-Type' => 'application/vnd.api+json',
]);
});
// In use:
return fractal($comments, new CommentTransformer)
->withResourceName('comments')
->respondJsonApi();
For quick links to fresh content, and for more thoughts that don't make it to the blog.