Objectif
Construire une API REST complète de gestion de tâches avec resources, form requests, pagination, filtres, et gestion centralisée des erreurs.
| Concept | Mis en pratique |
| API Resource | TaskResource avec whenLoaded() |
| Form Request | StoreTaskRequest / UpdateTaskRequest |
| Pagination | cursorPaginate() sur grandes listes |
| Filtres | ?status=todo&priority=high&project_id=1 |
| Versioning | Préfixe /api/v1/ sur toutes les routes |
| Erreurs JSON | Centralisé dans bootstrap/app.php |
Endpoints de l'API
| Méthode | URL | Auth | Description |
| POST | /api/v1/auth/login | — | Obtenir un token |
| GET | /api/v1/tasks | Bearer | Lister (paginé, filtrable) |
| POST | /api/v1/tasks | Bearer + write | Créer une tâche |
| GET | /api/v1/tasks/{id} | Bearer | Détail avec relations |
| PUT | /api/v1/tasks/{id} | Bearer + write | Modifier |
| DELETE | /api/v1/tasks/{id} | Bearer + write | Supprimer (204) |
| GET | /api/v1/projects/{id}/tasks | Bearer | Tâ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
- Créer la migration
tasks : title, body, status (enum), priority (enum), due_date, user_id, project_id
- Créer
TaskResource avec whenLoaded('project') et calcul is_overdue
- Créer
StoreTaskRequest avec authorize() + rules() + failedValidation()
- Implémenter les filtres dans
index() avec when($request->status, ...)
- Utiliser
cursorPaginate() pour la liste
- 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();
}
}