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.

Create a free account to save this page, take notes and track your progress. Sign in

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.

Shaping API output with resources
<?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?

  1. Resources are faster
  2. To control exactly which fields the API exposes and how they're formatted
  3. Models can't be converted to JSON
  4. Resources validate input
Show answer

Answer: To control exactly which fields the API exposes and how they're formatted

Was this page helpful?