Вы наверняка видели эти пять слов в спецификациях и коде. GET, POST, PUT, PATCH, DELETE. Кто-то использует первые два и закрывает все потребности. Кто-то путает PUT и PATCH. А кто-то пихает JSON в GET-запрос и удивляется, что балансировщик режет тело запроса.
Метод в HTTP — не просто техническая деталь. Это семантический контракт между клиентом и сервером. Выбор метода говорит серверу: «я хочу прочитать», «я хочу создать», «я хочу заменить целиком» или «поправь вот это поле». Сервер, в свою очередь, обещает вести себя предсказуемо: отдавать одни и те же данные на повторный GET, не плодить дубликаты при повторном PUT и так далее.
Если вы ещё не разобрались с базовыми принципами REST — начните с основных принципов REST API. А текущий пост — про методы: их семантика, свойства, ошибки и дерево решений.
Что такое HTTP-методы и зачем они нужны
HTTP-метод — это глагол, который задаёт намерение запроса. Представьте официанта в ресторане. Вы можете попросить меню (GET), заказать новое блюдо (POST), попросить заменить остывший стейк целиком (PUT), попросить убрать лук из салата (PATCH) или отменить заказ (DELETE). Глагол определяет, что произойдёт с ресурсом на сервере.
Формально методы описаны в RFC 7231 (и дополнены в RFC 5789 для PATCH). Но на практике достаточно знать пять: GET, POST, PUT, PATCH и DELETE. Остальные (HEAD, OPTIONS, TRACE, CONNECT) используются реже и решающим образом на архитектуру не влияют.
Важно: метод ничего не гарантирует сам по себе. Сервер может реализовать GET, удаляющий запись из базы, — и HTTP-спецификация не запрещает. Но это нарушение семантики, и ваш API быстро превратится в минное поле. Семантика методов — это конвенция, которую все ожидают, и ломать её без крайней нужды не стоит (а с крайней нуждой стоит пересмотреть архитектуру).
Пять методов: полный разбор
GET — читаем, не вредим
GET — самый частый метод в REST API. Его задача: получить представление ресурса и ничего не изменить.
Ключевые свойства:
- Безопасный (safe) — не меняет состояние сервера. Повторный GET на тот же URL должен возвращать те же данные (с поправкой на время).
- Идемпотентный (idempotent) — сколько бы раз вы ни повторили запрос, результат один и тот же. Повторный GET не создаст дубликатов, не изменит счётчиков, не спишет деньги.
- Кэшируемый — браузеры, CDN и прокси могут кэшировать ответы на GET. Это один из столпов производительности REST.
Пример:
GET /api/orders/42 HTTP/1.1Authorization: Bearer eyJhbGciOi...Что возвращает: заказ №42 в формате JSON. Никаких побочных эффектов.
GET /api/orders?status=active&page=1&limit=20 HTTP/1.1Что возвращает: первую страницу активных заказов. Фильтрация и пагинация передаются в query-параметрах.
Ошибка, которую допускают: передавать тело в GET-запросе. HTTP-спецификация этого не запрещает, но семантика GET говорит: «я читаю, а не отправляю данные». Балансировщики, кэширующие прокси и некоторые библиотеки тело GET-запроса просто игнорируют или отбрасывают. Результат: ваш фильтр { "status": "active" } в теле запроса не долетает до сервера, а вы гадаете, почему API возвращает все заказы подряд.
POST — создаём новое
POST — второй по частоте метод. Его задача: создать новый ресурс или выполнить операцию, не сводимую к CRUD.
Ключевые свойства:
- Небезопасный — меняет состояние сервера. Повторный POST создаст ещё один ресурс, заспамит базу дубликатами или, того хуже, инициирует повторный платёж.
- Неидемпотентный — каждый вызов даёт разный результат. Про это свойство часто забывают и потом разгребают последствия.
Пример создания заказа:
POST /api/orders HTTP/1.1Content-Type: application/json
{ "customer_id": 42, "items": [ {"product_id": 101, "quantity": 2}, {"product_id": 205, "quantity": 1} ]}Что возвращает: 201 Created с заголовком Location: /api/orders/789 и телом созданного заказа.
POST — единственный метод, который не обязан быть идемпотентным. Если вам нужно создать ресурс и гарантировать отсутствие дублей при повторной отправке (например, платёж), — это отдельная задача, решаемая через ключ идемпотентности (но об этом в другом посте).
Отдельный сценарий для POST — операции, которые не вписываются в CRUD:
POST /api/orders/789/send-to-fulfillment HTTP/1.1Здесь мы не создаём ресурс, а запускаем бизнес-операцию: «отправить заказ на сборку». REST в чистом виде предлагает моделировать такие операции как создание подресурса (например, POST /api/orders/789/fulfillments). На практике многие команды используют POST для RPC-подобных вызовов — и это работает, пока вы осознаёте, что отходите от REST-конвенций.
PUT — перезаписываем целиком
PUT — метод для полной замены ресурса. Отправляете полное представление объекта, и сервер записывает его вместо старого.
Ключевые свойства:
- Небезопасный — меняет состояние.
- Идемпотентный — повторный PUT с тем же телом даст тот же результат. Это важно: PUT гарантирует, что сколько бы раз ни пришёл один и тот же запрос, итоговое состояние ресурса не изменится.
Пример: заказчик меняет адрес доставки. Вы отправляете полный заказ с новым адресом:
PUT /api/orders/789 HTTP/1.1Content-Type: application/json
{ "id": 789, "customer_id": 42, "items": [...], "address": "г. Москва, ул. Новая, д. 10"}Что возвращает: 200 OK с обновлённым заказом. Если ресурса не существовало — некоторые API создают его и возвращают 201 Created. Но это уже спорная практика: строгий REST предполагает, что клиент знает URL создаваемого ресурса заранее.
Важный нюанс: PUT требует полного представления. Если вы отправите заказ без поля items, сервер имеет право (и должен, по спецификации) затереть товары в заказе пустым массивом. Именно из-за этого PUT часто заменяют на PATCH — когда нужно обновить одно-два поля, а не гонять весь объект туда-сюда.
PATCH — обновляем частично
PATCH — метод для частичного обновления ресурса. В отличие от PUT, вы отправляете только те поля, которые изменились.
Ключевые свойства:
- Небезопасный — меняет состояние.
- Неидемпотентный в общем случае. Важно: PATCH может быть идемпотентным, но не обязан. Если вы отправляете
{"status": "delivered"}— повторный вызов ничего не сломает (статус уже delivered). Но если вы отправляете{"$inc": {"counter": 1}}— каждый вызов увеличит счётчик. Всё зависит от формата PATCH-документа.
Два популярных формата PATCH:
JSON Merge Patch (RFC 7396) — простой: отправляете JSON с полями, которые хотите обновить. Не позволяет удалить поле (можно только установить в null).
PATCH /api/orders/789 HTTP/1.1Content-Type: application/merge-patch+json
{ "address": "г. Москва, ул. Новая, д. 15", "status": "confirmed"}JSON Patch (RFC 6902) — операции над документом: добавить, заменить, удалить, переместить.
PATCH /api/orders/789 HTTP/1.1Content-Type: application/json-patch+json
[ { "op": "replace", "path": "/address", "value": "г. Москва, ул. Новая, д. 15" }, { "op": "remove", "path": "/discount_code" }]JSON Patch мощнее, но сложнее для клиента. Merge Patch интуитивен, но не позволяет удалить поле (только обнулить). Выбор — всегда компромисс между гибкостью API и простотой интеграции.
На практике многие команды используют PATCH с Merge Patch для 95% сценариев. JSON Patch оправдан, когда у вас сложные документы и нужна атомарность операций над разными полями.
DELETE — удаляем
DELETE — метод для удаления ресурса.
Ключевые свойства:
- Небезопасный — меняет состояние (очевидно).
- Идемпотентный — повторный DELETE на тот же URL не должен падать с ошибкой. Первый вызов удаляет, второй — возвращает
404 Not Foundили204 No Content, и оба варианта корректны. Главное, чтобы сервер не упал с исключением «ресурс уже удалён».
Пример:
DELETE /api/orders/789 HTTP/1.1Что возвращает: 204 No Content (успех, но тела нет) или 200 OK с подтверждением удаления. Второй вариант полезен, когда клиенту нужно показать удалённые данные (например, в undo-операции).
Нюанс: удалять ли связанные ресурсы — архитектурное решение. Удаление заказа может каскадно удалить позиции заказа, а может оставить их осиротевшими. Первый подход консистентен, второй позволяет восстановить заказ. Выбирайте по бизнес-требованиям.
Безопасность и идемпотентность
Два свойства, которые чаще всего путают. Давайте разложим.
- Безопасный метод — не меняет состояние сервера. Только GET и HEAD (и OPTIONS).
- Идемпотентный метод — повторный вызов с теми же параметрами даёт тот же результат. GET, PUT, DELETE — идемпотентны. POST — нет. PATCH — зависит от документа.
Безопасный метод всегда идемпотентен, но не наоборот. PUT идемпотентен (повторная замена даёт то же состояние), но не безопасен (состояние меняется).
На диаграмме видно чёткое разделение: GET (зелёный) — единственный полностью безопасный метод. POST (фиолетовый) — единственный гарантированно неидемпотентный. PUT и DELETE — небезопасные, но идемпотентные. PATCH — дитя компромисса: безопасным не назовёшь, с идемпотентностью тоже не всё однозначно.
Зачем вообще заморачиваться идемпотентностью? Представьте платёжный шлюз. Клиент отправил POST на списание средств, интернет моргнул, ответ не дошёл. Клиент повторяет запрос. Если метод неидемпотентен — спишется дважды. Если идемпотентен (PUT с полным состоянием или POST с ключом идемпотентности) — повторный запрос безопасен. В мире распределённых систем, где сеть ненадёжна по определению, идемпотентность — не роскошь, а средство выживания.
Таблица сравнения
| Метод | Назначение | Безопасный | Идемпотентный | Кэшируемый | Типичный код ответа |
|---|---|---|---|---|---|
| GET | Чтение ресурса | Да | Да | Да | 200 OK |
| POST | Создание, RPC-операции | Нет | Нет | Нет | 201 Created |
| PUT | Полная замена | Нет | Да | Нет | 200 / 204 |
| PATCH | Частичное обновление | Нет | Зависит | Нет | 200 OK |
| DELETE | Удаление | Нет | Да | Нет | 204 No Content |
Если хотите детально разобраться, какие коды ответов возвращать в каждом сценарии — загляните в справочник HTTP-кодов ответов. Там разобраны все 1xx–5xx с примерами из реальной практики.
Как выбрать метод: дерево решений
Никакой магии — просто логические вопросы:
- Вы читаете данные и не меняете состояние? → GET
- Создаёте новый ресурс, и сервер назначает ID? → POST
- Заменяете ресурс целиком, и клиент знает URL? → PUT
- Меняете одно-два поля, не гоняя весь объект? → PATCH
- Удаляете ресурс? → DELETE
Универсальное правило, которое выручает в спорных ситуациях: если вы сомневаетесь между POST и PUT — спросите себя, приведёт ли повторный запрос к дубликату. Если да — используйте PUT или добавьте ключ идемпотентности к POST.
Типичные ошибки
GET с телом запроса. Технически HTTP позволяет. На практике — балансировщики, кэши и прокси режут тело GET как нестандартное поведение, и ваши параметры фильтрации теряются где-то по дороге. Фильтры, сортировку и пагинацию передавайте в query-параметрах.
POST для всего. Команда договорилась: «Пишем всё через POST, чтобы не заморачиваться». Через полгода методология «POST для всего» аукается. Кэширование не работает (POST не кэшируется). Идемпотентности нет (повторные запросы плодят дубликаты). Отладка превращается в гадание: этот POST что-то создаёт или обновляет? Экономия пяти минут на выборе метода оборачивается часами дебага.
PUT для частичного обновления. Отправляете PUT /api/orders/789 с {"status": "delivered"} — и сервер затирает весь заказ, оставляя только поле статуса. То, что у вас в коде контроллер додумывает недостающие поля из базы — не заслуга PUT, а костыль, который сломается при смене разработчика. Используйте PATCH для частичных обновлений.
Игнорирование кодов ответа. 200 OK на создание ресурса вместо 201 Created — мелочь? Нет. Клиентские библиотеки, мониторинг и автоматические тесты полагаются на семантику кодов. Если ваш API на создание возвращает 200 — автоматическая проверка «создался ли ресурс» усложняется: приходится парсить тело ответа вместо проверки статус-кода.
Заключение
Пять методов — это не бюрократия и не «перегруз» для простого API. Это язык, на котором клиент и сервер договариваются о намерениях. GET значит «дай посмотреть». POST — «создай». PUT — «замени целиком». PATCH — «подправь деталь». DELETE — «удали». Просто. Понятно. Предсказуемо.
Стоит один раз договориться в команде о семантике методов — и REST API перестаёт быть источником сюрпризов. А сюрпризы в продакшене — это именно то, чего мы все пытаемся избежать, не так ли?
PS. Если вы дочитали до этого места и узнали свой API в разделе «Типичные ошибки» — не переживайте. Мы все через это проходили. Главное — не продолжать.