Короткий ответ
Вложенный путь (/users/{id}/orders) уместен, когда ресурс логически не существует без родителя и к нему всегда обращаются в его контексте; плоский путь с фильтром (/orders?user_id={id}) лучше, когда ресурс самостоятелен и нужно выбирать его по разным критериям, не только по родителю.
Как это работает подробнее
вложенность пути отражает владение (ownership) — заказ существует только в контексте пользователя, который его создал. Но если нужно искать заказы одновременно по пользователю, статусу и дате — чисто вложенный путь усложняет комбинирование фильтров, а плоский с query-параметрами делает это естественно.
Пример кода
example.py
from fastapi import FastAPI
app = FastAPI()
# Создание в контексте родителя — вложенность оправдана
@app.post("/users/{user_id}/orders")
def create_order(user_id: int, payload: dict):
return create_order_for_user(user_id, payload)
# Доступ к конкретному заказу — по глобально уникальному ID, без родителя в пути
@app.get("/orders/{order_id}")
def get_order(order_id: int):
return find_order(order_id)
# Поиск по нескольким критериям — плоский путь с фильтрами
@app.get("/orders")
def list_orders(user_id: int | None = None, status: str | None = None):
return search_orders(user_id=user_id, status=status)Что использовать на практике
- Рекомендация. глубокую вложенность (
/users/{id}/orders/{oid}/items/{iid}) стоит ограничивать одним-двумя уровнями — иначе URL становятся громоздкими и ломкими при реорганизации модели данных. Часто практичнее делатьGET /orders/{oid}без родителя в пути (ресурс сам по себе уникален по ID), используя вложенность только для создания (POST /users/{id}/orders) или списков в контексте родителя.
Подводные камни
- Риски. требовать родительский ID в пути для операций с конкретным дочерним ресурсом, когда дочерний ID уже глобально уникален (это заставляет клиента хранить лишний контекст); смешивать вложенный путь и отдельный плоский эндпоинт для одного и того же ресурса без единого источника истины, что увеличивает поверхность API и путает клиентов.