Learnly
Проектирование API/Глава 12/Урок 1

Проектирование эндпоинтов: именование, версионирование, вложенность

Проектирование эндпоинтов: именование, версионирование, вложенность

Хороший URL, это контракт. Он должен быть понятен без документации, предсказуем и устойчив к изменениям. Плохой URL заставляет команду держать в голове тонны неочевидных правил.


Именование ресурсов

Правило 1: Существительные, не глаголы

URL описывает ресурс, а не действие. Действие выражается HTTP-методом.

✅ GET    /api/v1/orders
✅ POST   /api/v1/orders
✅ DELETE /api/v1/orders/42

❌ GET /api/v1/getOrders
❌ POST /api/v1/createOrder
❌ POST /api/v1/deleteOrder?id=42

Антипаттерн с глаголами, это RPC поверх HTTP. Если ты пишешь /getUser или /sendEmail в URL, это не REST.

Правило 2: Множественное число

Ресурсы, коллекции. Используй множественное число всегда.

✅ /users     ✅ /products     ✅ /orders
❌ /user      ❌ /product

Исключение, синглтоны (ресурс существует в одном экземпляре):

/users/42/profile   → профиль конкретного пользователя (один на пользователя)

Правило 3: lowercase и дефисы

✅ /api/v1/product-categories
✅ /api/v1/user-profiles

❌ /api/v1/productCategories   (camelCase в URL)
❌ /api/v1/product_categories  (underscore)

Правило 4: Никаких расширений файлов

✅ GET /api/v1/reports/42
   Accept: application/pdf   ← формат через заголовок

❌ /api/v1/reports/42.pdf

Вложенность (Nested Resources)

Вложенные ресурсы отражают отношения «один ко многим»:

/users/42/orders          → заказы пользователя 42
/orders/17/items          → позиции заказа 17
/categories/5/products    → товары в категории 5

Когда вкладывать

  • Вложенный ресурс не имеет смысла без родителя, /orders/17/items
  • Нужно явно выразить владение, /users/42/orders vs /orders?user_id=42

Когда НЕ вкладывать

❌ /users/42/orders/17/items/3/product/category

Глубже 2 уровней, почти всегда проблема в дизайне. Правило: не более 2 уровней вложенности.

Альтернатива, плоские URL с фильтрами:

✅ /order-items?order_id=17
✅ /products?category_id=5

Версионирование API

Версионирование, это страховка: ты можешь менять API, не ломая существующих клиентов.

Стратегия 1: Версия в URL (рекомендуется)

https://api.example.com/v1/users
https://api.example.com/v2/users

Используют Stripe, GitHub, Twilio, 90% крупных публичных API. Почему: очевидно в URL, легко тестировать в браузере, хорошо кэшируется.

Стратегия 2: Версия в заголовке

GET /users/42
Accept: application/vnd.myapi.v2+json

Теоретически чище (URL идентифицирует ресурс, не версию), но неудобно тестировать и хуже кэшируется.

Стратегия 3: Версия в query parameter

GET /users/42?version=2

Используется реже. Проблема: query params семантически для фильтрации данных, а не для роутинга.

Вывод: для публичных API, версия в URL. Это самый понятный вариант для потребителей.


Действия которые не вписываются в CRUD

Иногда нужно выразить действие которое не является простым созданием/чтением/изменением/удалением.

Паттерн 1: Вложенный глагол-ресурс

POST /orders/17/cancel      → отменить заказ
POST /users/42/activate     → активировать аккаунт
POST /payments/99/refund    → вернуть деньги

cancel, activate, refund, «действия» оформленные как ресурсы. POST сигнализирует о создании события.

Паттерн 2: Изменение состояния через PATCH

PATCH /orders/17
{ "status": "cancelled" }

Семантически чисто, меняем поле status. Хорошо для конечных автоматов.

Паттерн 3: Отдельный ресурс для события

POST /order-cancellations   → создаём факт отмены как отдельную сущность
POST /refunds               → создаём возврат

Хорошо когда событие само по себе важная сущность, нужно хранить историю.


Частые ошибки проектирования из реальной практики

Ошибка 1: Глагол в URL

❌ POST /api/sendEmail
✅ POST /api/emails          (создаём письмо, система его отправляет)

Ошибка 2: Действие в GET-запросе

❌ GET /api/users/42/delete  (GET не должен менять данные)
✅ DELETE /api/users/42

Ошибка 3: Нет версионирования с первого дня

Добавить /v1/ проще сразу, чем мигрировать сотни клиентов потом.

Ошибка 4: Непоследовательность

❌ /api/v1/users/42          (ID в пути)
❌ /api/v1/products?id=5     (ID в query param)
✅ /api/v1/users/42
✅ /api/v1/products/5

Итоговые правила

Правило Плохо Хорошо
Существительные /getUser /users
Множественное число /user/42 /users/42
lowercase + дефис /userProfiles /user-profiles
Глубина вложенности /a/1/b/2/c/3 /a/1/b/2
Версия с первого дня /users /v1/users
Формат не в URL /report.pdf /report + Accept header
AI-тест
1 / 16

Как следует именовать ресурсы в URL?