Вкладений ресурс відображає відношення «належить до» в URL:
GET /posts/42/comments # коментарі конкретного поста
POST /posts/42/comments # додати коментар до поста
Коли вкладеність доречна:
- дочірній ресурс не має сенсу без батька (коментар без поста, позиція без замовлення);
- створення - URL одразу задає батька, і його не треба передавати в тілі;
- колекція в контексті: «замовлення цього клієнта» - природний запит.
Де зупинитися:
1. Не глибше одного рівня. /users/7/orders/42/items/3/discounts/1 - важко читати, і всі проміжні ідентифікатори доводиться знати й перевіряти. Якщо в дочірнього ресурсу є власний унікальний id, на ньому можна працювати напряму:
GET /posts/42/comments # колекція - вкладена
GET /comments/15 # окремий коментар - плаский
PATCH /comments/15
DELETE /comments/15
Це неглибока вкладеність (shallow nesting): вкладені лише ті маршрути, де батько потрібен (колекція й створення). У Laravel - Route::resource('posts.comments', CommentController::class)->shallow().
2. Не для фільтрації за довільними полями. /users/7/orders - добре, але /status/paid/orders - ні: це фільтр, а не ієрархія, - GET /orders?status=paid.
3. Не для зв'язків «багато-до-багатьох» без явної власності: /tags/5/posts і /posts/42/tags - обидва варіанти доречні як точки входу, але сам зв'язок - окремий ресурс.
Безпека - головна пастка вкладених URL:
GET /posts/42/comments/15
Перевірити треба не лише, що користувач має доступ до поста 42, а й що коментар 15 належить посту 42. Інакше зловмисник підставить пост, до якого має доступ, і чужий коментар. У Laravel для цього - scopeBindings():
Route::get('/posts/{post}/comments/{comment}', ...)->scopeBindings();
Тоді коментар шукається через $post->comments(), і чужий дасть 404.
Консистентність: якщо вже обрано схему (вкладені колекції + плаский доступ до елементів), - дотримуватися її для всіх ресурсів API.