REST это стиль, а не протокол: API строится вокруг ресурсов (сущностей), к которым применяются стандартные методы HTTP. Хорошее API предсказуемо: зная пару эндпоинтов, угадываешь остальные.
Ресурсы и URL
GET /orders список
POST /orders создать
GET /orders/981 один заказ
PATCH /orders/981 частично изменить
DELETE /orders/981 удалить
GET /orders/981/items вложенный ресурс
- Существительные во множественном числе, без глаголов: не
/getOrders, не/orders/create. - Действия, которые не ложатся в CRUD, оформляют как подресурс:
POST /orders/981/cancelили как смену поляPATCH /orders/981 {"status":"cancelled"}. - Фильтры, сортировка и поиск в query:
/orders?status=paid&sort=-createdAt.
Коды ответов
201 Created + Location при создании, 204 при удалении, 404 нет ресурса, 409 конфликт (email занят), 422 или 400 при невалидных данных, 401 и 403, 429 при превышении лимита. Ошибки в одном формате, например Problem Details (RFC 9457):
{ "type": "https://api.example/errors/validation", "title": "Validation failed",
"status": 422, "errors": [{ "field": "email", "message": "required" }] }
Пагинация
Offset: ?limit=20&offset=40. Просто, можно прыгнуть на страницу 7. Минусы: OFFSET 100000 медленный (базе надо пропустить строки), а если между запросами добавились записи, элементы на границе страниц дублируются или теряются.
Cursor (keyset): ?limit=20&after=eyJpZCI6OTgxfQ. Курсор кодирует позицию (например, createdAt и id последнего элемента), запрос WHERE (created_at, id) < (...) ORDER BY ... LIMIT 20 работает по индексу одинаково быстро на любой глубине и стабилен при вставках. Минус: нельзя прыгнуть на произвольную страницу. Подходит для лент и бесконечной прокрутки.
Версионирование
- В пути:
/v1/orders. Просто и наглядно, самый частый вариант. - В заголовке:
Accept: application/vnd.example.v2+jsonили свой заголовок. - Главное правило: не ломать клиентов. Добавить необязательное поле можно без новой версии. Удалить или переименовать поле, сменить тип или смысл это breaking change.
Идемпотентность и надёжность
Сеть рвётся, клиент повторяет запрос. GET, PUT, DELETE повторять безопасно. Для POST, создающего платёж или заказ, клиент шлёт Idempotency-Key, сервер хранит результат по ключу и на повтор отдаёт его же. Для конкурентных изменений: ETag + If-Match (оптимистическая блокировка). Для защиты от перегрузки: rate limit с 429 и Retry-After.
REST, GraphQL, gRPC
- REST: просто, кешируется HTTP-средствами, понятен всем. Минусы: over-fetching (пришло лишнее) и under-fetching (нужно 5 запросов для одного экрана).
- GraphQL: один эндпоинт, клиент сам описывает, какие поля нужны. Удобно для сложных экранов и многих клиентов. Минусы: HTTP-кеш почти не работает (обычно POST на один URL), нужна защита от тяжёлых запросов (глубина, сложность), проблема N+1 на сервере (решается DataLoader).
- gRPC: бинарный Protobuf поверх HTTP/2, строгий контракт, стриминг, кодогенерация. Хорош между сервисами. Из браузера напрямую не работает, нужен gRPC-Web и прокси.
Частые ошибки
- Глаголы в URL и
POSTна всё. - Пагинация без лимита сверху: клиент просит
limit=1000000. - Разный формат ошибок в разных эндпоинтах.
- Breaking change без новой версии.
Что спросят на собесе
- Какие принципы REST ты знаешь, как назвать эндпоинты для заказа и его отмены?
- Offset или cursor пагинация: плюсы и минусы.
- Как версионировать API и что считается breaking change?
- Как сделать безопасный повтор создания платежа?
- Когда выбрать GraphQL или gRPC вместо REST?