Перейти к содержанию
Innovialabs

Проектирование REST API: именование, версии, ошибки

Практика3 мин

Коротко

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

Главный принцип: предсказуемость

Хороший API понятен без чтения документации к каждому эндпоинту. Разработчик увидел два адреса — и уже догадывается, как устроен третий. Ниже правила, которые дают этот эффект.

Именование URL

Правила:

  • Существительные во множественном числе: /products, /orders.
  • Без глаголов: действие задаёт HTTP-метод.
  • kebab-case и нижний регистр: /delivery-zones, а не /DeliveryZones.
  • Вложенность не глубже одного уровня: /orders/15/items — нормально, /users/3/orders/15/items/2/reviews — уже тяжело.
ПлохоХорошо
GET /getAllProductsGET /products
POST /createOrderPOST /orders
POST /deleteUser?id=3DELETE /users/3
GET /Product_ListGET /products

Для действий, которые не укладываются в CRUD, допустим подресурс: POST /orders/15/cancel. Главное — один стиль на весь API.

Пагинация

Никогда не отдавайте коллекцию целиком: данные растут, и однажды ответ станет огромным.

Два распространённых подхода:

  • Offset: GET /products?limit=20&offset=40. Просто, позволяет прыгать на нужную страницу, но медленнеет на больших таблицах и может «терять» записи при вставках.
  • Cursor: GET /products?limit=20&cursor=eyJpZCI6NDJ9. Стабилен для лент и больших объёмов, но нельзя перейти сразу на страницу 10.

Ответ должен содержать данные и информацию для следующего шага:

{
  "data": [{ "id": 41, "name": "Чайник" }],
  "pagination": { "nextCursor": "eyJpZCI6NDJ9", "hasMore": true }
}

Задайте максимальный limit на сервере, чтобы клиент не запросил миллион записей.

Фильтрация и сортировка

Используйте query-параметры с понятными именами:

GET /products?category=kitchen&minPrice=100000&sort=-createdAt
  • Фильтры — обычные параметры с именами полей.
  • Сортировка — один параметр sort, минус означает убывание.
  • Поиск — отдельный параметр, например q.
  • Неизвестный параметр лучше отклонить с 400, чем молча проигнорировать.

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

API, которым пользуются внешние клиенты, рано или поздно изменится несовместимо. Версия позволяет не ломать старых клиентов.

  • В URL: /v1/products — самый наглядный и простой вариант.
  • В заголовке: Accept: application/vnd.example.v2+json — чище URL, но сложнее отлаживать.

Что считать ломающим изменением: удаление или переименование поля, смена типа, новое обязательное поле в запросе. Добавление нового необязательного поля в ответ версию обычно не требует. Старую версию объявляйте устаревшей заранее и давайте клиентам время на переход.

Единый формат ошибок

Все эндпоинты должны возвращать ошибки одинаково. Удобная основа — стандарт Problem Details (RFC 9457):

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "Поле price должно быть больше нуля",
  "errors": [{ "field": "price", "message": "must be > 0" }]
}

Правила:

  • Правильный HTTP-код всегда, а не 200 с ошибкой внутри.
  • Машиночитаемый тип или код ошибки, чтобы клиент мог на него реагировать.
  • Понятное сообщение для разработчика.
  • Никаких stack trace и SQL-запросов в ответе.

Ключи идемпотентности

Для операций, повтор которых опасен — оплата, создание заказа, — клиент передаёт уникальный ключ:

POST /payments
Idempotency-Key: 7f3c2a90-1b4e-4d2a-9c11-5e8f0a6b7d21

Сервер сохраняет результат по этому ключу. Если запрос пришёл повторно — из-за таймаута или двойного клика — сервер возвращает уже сохранённый ответ, а не создаёт второй платёж.

Частые ошибки

  • Разные стили в одном API: camelCase в одних ответах, snake_case в других.
  • Даты в локальном формате. Используйте ISO 8601 с часовым поясом.
  • Отдача внутренних id и полей базы как есть. API — это контракт, а не дамп таблицы.
  • Нет документации. Спецификация OpenAPI решает это и позволяет генерировать клиентов.

Частые вопросы

Для админок и таблиц с переходом по страницам подходит offset. Для лент, бесконечной прокрутки и больших данных надёжнее cursor.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.