Laravel Tutorial · Requests and Responses · Lesson 10 of 27
Responses, JSON and API Resources
Build responses with status codes and headers, return JSON, and shape your API output with API Resource classes instead of exposing models directly.
Building responses
response('text', 201),response()->json($data, 201),response()->noContent()(204)->header('X-Name', 'value'),->cookie(...)redirect('/home'),redirect()->route('posts.show', $post),back()->with('status', 'Saved!')response()->download($path),response()->streamDownload(...)abort(404)/abort_if($condition, 403)to stop with an HTTP error
API Resources
Returning a model directly exposes every column (including ones you add later). An API Resource (php artisan make:resource UserResource) decides exactly what the API returns, can rename and format fields, include relationships only when loaded, and add metadata. Collections are wrapped in a data key.
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
Schema::create('members', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->string('password_hash');
$table->boolean('is_admin')->default(false);
$table->timestamps();
});
class Member extends Model
{
protected $guarded = [];
}
class MemberResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => $this->is_admin ? 'admin' : 'member', // renamed and formatted
]; // password_hash never leaves the server
}
}
Member::create(['name' => 'Ana', 'email' => '[email protected]', 'password_hash' => 'secret', 'is_admin' => true]);
Member::create(['name' => 'Ben', 'email' => '[email protected]', 'password_hash' => 'secret']);
Route::get('/raw/{member}', fn (Member $member) => $member->only('name', 'password_hash'));
Route::get('/members/{member}', fn (Member $member) => new MemberResource($member));
Route::get('/members', fn () => MemberResource::collection(Member::orderBy('name')->get())->additional(['meta' => ['count' => Member::count()]]));
visit('GET', '/raw/1'); // what a careless endpoint would leak
visit('GET', '/members/1');
visit('GET', '/members');
Output
GET /raw/1 -> 200 {"name":"Ana","password_hash":"secret"}
GET /members/1 -> 200 {"data":{"id":1,"name":"Ana","email":"[email protected]","role":"admin"}}
GET /members -> 200 {"data":[{"id":1,"name":"Ana","email":"[email protected]","role":"admin"},{"id":2,"name":"Ben","email":"[email protected]","role":"member"}],"meta":{"count":2}}Run this on your computer — see “Get Started” for setup.
Test yourself
Why use an API Resource instead of returning a model?
Resources are fasterTo control exactly which fields the API exposes and how they're formattedModels can't be converted to JSONResources validate input
Show answer
Answer: To control exactly which fields the API exposes and how they're formatted