Objectif

Construire une API REST complète de gestion de tâches avec resources, form requests, pagination, filtres, et gestion centralisée des erreurs.

ConceptMis en pratique
API ResourceTaskResource avec whenLoaded()
Form RequestStoreTaskRequest / UpdateTaskRequest
PaginationcursorPaginate() sur grandes listes
Filtres?status=todo&priority=high&project_id=1
VersioningPréfixe /api/v1/ sur toutes les routes
Erreurs JSONCentralisé dans bootstrap/app.php

Endpoints de l'API

MéthodeURLAuthDescription
POST/api/v1/auth/loginObtenir un token
GET/api/v1/tasksBearerLister (paginé, filtrable)
POST/api/v1/tasksBearer + writeCréer une tâche
GET/api/v1/tasks/{id}BearerDétail avec relations
PUT/api/v1/tasks/{id}Bearer + writeModifier
DELETE/api/v1/tasks/{id}Bearer + writeSupprimer (204)
GET/api/v1/projects/{id}/tasksBearerTâches d'un projet
# Exemples curl
# Login
curl -X POST http://localhost/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"user@test.com","password":"password"}'

# Lister avec filtres
curl http://localhost/api/v1/tasks?status=todo&priority=high&per_page=5 \
  -H "Authorization: Bearer {token}"

# Créer
curl -X POST http://localhost/api/v1/tasks \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"title":"Implement OAuth","status":"todo","priority":"high","due_date":"2025-12-31"}'

Consignes

  1. Créer la migration tasks : title, body, status (enum), priority (enum), due_date, user_id, project_id
  2. Créer TaskResource avec whenLoaded('project') et calcul is_overdue
  3. Créer StoreTaskRequest avec authorize() + rules() + failedValidation()
  4. Implémenter les filtres dans index() avec when($request->status, ...)
  5. Utiliser cursorPaginate() pour la liste
  6. Centraliser les erreurs 401/403/404/422 dans bootstrap/app.php

Solution commentée

// app/Http/Resources/TaskResource.php
namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class TaskResource extends JsonResource
{
    public function toArray(Request $request): array {
        return [
            'id'          => $this->id,
            'title'       => $this->title,
            'body'        => $this->body,
            'status'      => $this->status,
            'priority'    => $this->priority,
            'due_date'    => $this->due_date?->toDateString(),
            'is_overdue'  => $this->due_date?->isPast() && $this->status !== 'done',
            'created_at'  => $this->created_at->toIso8601String(),
            'project'     => new ProjectResource($this->whenLoaded('project')),
            'assignee'    => new UserResource($this->whenLoaded('assignee')),
        ];
    }
}
// app/Http/Requests/StoreTaskRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class StoreTaskRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array {
        return [
            'title'      => 'required|string|max:255',
            'body'       => 'nullable|string',
            'status'     => 'in:todo,in_progress,done',
            'priority'   => 'in:low,medium,high',
            'due_date'   => 'nullable|date|after:today',
            'project_id' => 'nullable|exists:projects,id',
        ];
    }

    protected function failedValidation(Validator $validator): void {
        throw new HttpResponseException(
            response()->json([
                'message' => 'Données invalides.',
                'errors'  => $validator->errors(),
            ], 422)
        );
    }
}

// app/Http/Controllers/Api/TaskController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreTaskRequest;
use App\Http\Resources\TaskResource;
use App\Models\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index(Request $request) {
        $tasks = Task::with(['project', 'assignee'])
            ->where('user_id', $request->user()->id)
            ->when($request->status,   fn($q) => $q->where('status', $request->status))
            ->when($request->priority, fn($q) => $q->where('priority', $request->priority))
            ->when($request->project_id, fn($q) => $q->where('project_id', $request->project_id))
            ->when($request->search, fn($q) => $q->where('title', 'like', "%{$request->search}%"))
            ->latest()
            ->cursorPaginate($request->per_page ?? 15);

        return TaskResource::collection($tasks);
    }

    public function store(StoreTaskRequest $request) {
        $task = Task::create([
            ...$request->validated(),
            'user_id' => $request->user()->id,
        ]);
        return (new TaskResource($task))->response()->setStatusCode(201);
    }

    public function show(Task $task) {
        $this->authorize('view', $task);
        $task->load('project', 'assignee');
        return new TaskResource($task);
    }

    public function update(StoreTaskRequest $request, Task $task) {
        $this->authorize('update', $task);
        $task->update($request->validated());
        return new TaskResource($task);
    }

    public function destroy(Task $task) {
        $this->authorize('delete', $task);
        $task->delete();
        return response()->noContent();
    }
}
← Cours Module 06 🧠 QCM Module 07 →