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

Принципы REST: HTTP-методы, статус-коды, идемпотентность

Принципы REST: HTTP-методы, статус-коды, идемпотентность

REST, это не библиотека и не протокол. Это архитектурный стиль с шестью принципами. Из них три самых важных для повседневной работы: единообразный интерфейс, клиент-серверное разделение и stateless (без состояния). Именно они диктуют как правильно использовать HTTP.


HTTP-методы

Каждый HTTP-метод имеет чёткую семантику. Нарушать её, значит создавать API, который будет путать коллег и ломать кэши.

GET, получение данных

GET запрашивает ресурс. Он никогда не должен изменять данные на сервере.

GET /api/v1/products          → список товаров
GET /api/v1/products/42       → товар с id=42
GET /api/v1/users/5/orders    → заказы пользователя 5

GET-запросы кэшируются браузером и CDN. Если твой GET меняет данные, кэш сломает логику.

POST, создание ресурса

POST создаёт новый ресурс. URL не содержит ID, он присваивается сервером.

POST /api/v1/products
Content-Type: application/json

{
  "name": "Ноутбук Dell XPS",
  "price": 89999,
  "category_id": 3
}

Ответ, обычно 201 Created с заголовком Location или телом с созданным ресурсом:

HTTP/1.1 201 Created
Location: /api/v1/products/143

{
  "id": 143,
  "name": "Ноутбук Dell XPS",
  "price": 89999,
  "created_at": "2024-03-15T10:23:00Z"
}

PUT, полная замена ресурса

PUT полностью заменяет ресурс. Если поле не указано в теле, оно будет сброшено или стать null.

PUT /api/v1/products/143
Content-Type: application/json

{
  "name": "Ноутбук Dell XPS 15",
  "price": 94999,
  "category_id": 3
}

Правило: клиент должен отправить все поля ресурса, не только изменённые.

PATCH, частичное обновление

PATCH обновляет только те поля, которые указаны. Всё остальное остаётся как есть.

PATCH /api/v1/products/143
Content-Type: application/json

{
  "price": 84999
}

Только цена изменится. Название, категория и прочие поля останутся прежними.

PUT vs PATCH на практике:

  • Обновляешь весь объект целиком (форма редактирования) → PUT
  • Меняешь одно поле (toggle статуса, изменение цены) → PATCH

DELETE, удаление

DELETE /api/v1/products/143

Ответ: 204 No Content (удалено, тела нет) или 200 OK с подтверждением.

Итоговая таблица методов

Метод Действие Тело запроса Идемпотентный Безопасный
GET Получить Нет Да Да
POST Создать Да Нет Нет
PUT Полностью заменить Да Да Нет
PATCH Частично обновить Да Условно Нет
DELETE Удалить Нет/Да Да Нет

Идемпотентность

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

Математическая аналогия: умножение на 1 идемпотентно (5 × 1 = 5, и ещё раз 5 × 1 = 5). Прибавление 1, нет (5 + 1 = 6, ещё раз 6 + 1 = 7).

GET, идемпотентен. Сколько раз ни запроси /users/42, данные не меняются.

DELETE, идемпотентен. Удалить уже удалённый ресурс, то же самое состояние. Первый вызов вернёт 204, второй, 404, но состояние сервера одинаково: ресурса нет.

PUT, идемпотентен. Отправить одинаковое тело два раза, ресурс одинаков после обоих вызовов.

POST, не идемпотентен. Дважды отправить POST /orders создаст два заказа. Это критически важно при сетевых ошибках: если клиент не знает дошёл ли запрос, повтор POST может задублировать данные.

Почему это важно на практике

Клиент отправил POST /payments → сеть упала → ответ не получен
Клиент думает: "ошибка, нужно повторить"
Повторяет запрос → создаётся второй платёж

Решение для POST: idempotency key. Клиент генерирует уникальный ключ и передаёт в заголовке. Сервер запоминает результат по ключу и при повторе возвращает тот же ответ.

POST /api/v1/payments
Idempotency-Key: a8f3d2c1-4b5e-4c6d-8e7f-9a0b1c2d3e4f

{ "amount": 5000, "currency": "RUB" }

Именно так работают Stripe и другие платёжные системы.


HTTP статус-коды

Статус-код, это трёхзначное число, которое говорит клиенту что произошло. Правильные коды делают API самодокументируемым.

2xx, Успех

Код Название Когда использовать
200 OK GET, PUT, PATCH успешно выполнены
201 Created POST создал новый ресурс
204 No Content DELETE успешно, или PUT/PATCH без тела ответа
202 Accepted Запрос принят, обрабатывается асинхронно

3xx, Перенаправление

Код Название Когда использовать
301 Moved Permanently Ресурс навсегда переехал на новый URL
302 Found Временный редирект
304 Not Modified Кэш актуален, не пересылай данные

4xx, Ошибка клиента

Клиент сделал что-то не так. Повторять запрос без изменений бессмысленно.

Код Название Когда использовать
400 Bad Request Неверный формат запроса, ошибки валидации
401 Unauthorized Не аутентифицирован (нет токена или неверный)
403 Forbidden Аутентифицирован, но нет прав на действие
404 Not Found Ресурс не существует
409 Conflict Конфликт состояния (например, дубликат email)
422 Unprocessable Entity Синтаксис верен, но данные логически невалидны
429 Too Many Requests Rate limit превышен

5xx, Ошибка сервера

Клиент сделал всё правильно, но что-то сломалось на сервере. Повтор запроса может сработать.

Код Название Когда использовать
500 Internal Server Error Непредвиденная ошибка сервера
502 Bad Gateway Upstream сервис вернул некорректный ответ
503 Service Unavailable Сервис временно недоступен (деплой, перегрузка)
504 Gateway Timeout Upstream сервис не ответил вовремя

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

401 vs 403, распространённая путаница:

  • 401 Unauthorized → «Кто ты такой? Предъяви токен»
  • 403 Forbidden → «Знаю кто ты, но тебе сюда нельзя»

400 vs 422:

  • 400 → запрос синтаксически неверен (невалидный JSON)
  • 422 → JSON верный, но данные нарушают бизнес-правила

Не используй 200 для ошибок. Антипаттерн:

HTTP/1.1 200 OK
{ "success": false, "error": "User not found" }

Это ломает мониторинг, логирование и любую автоматизацию.


Принцип Stateless

REST требует, чтобы каждый запрос содержал всю необходимую информацию для его обработки. Сервер не хранит состояние между запросами.

# Правильно: токен в каждом запросе
GET /api/v1/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

# Неправильно: "ты же помнишь что я залогинился вчера"
GET /api/v1/profile

Stateless делает API горизонтально масштабируемым: любой из десяти серверов может обработать любой запрос, потому что вся информация уже в запросе.

AI-тест
1 / 14

Какой принцип REST отвечает за то, что клиент и сервер работают независимо друг от друга?