В главе 3.1 был общий принцип: PUT — перезапись целиком, PATCH — частичное обновление. Сейчас разбираем, как это выглядит руками и где обычно ошибаются.

Стартовая точка

Пусть на сервере есть пост:

GET /api/posts/42
→ {
    "id": 42,
    "title": "Старый заголовок",
    "body": "Старое тело",
    "author": "anna",
    "tags": ["css", "html"],
  }

Мы хотим поменять только title. Сравним два подхода.

Через PUT — шлём ресурс целиком

// Сначала достаём текущий ресурс:
const current = await fetch('/api/posts/42').then(r => r.json());

// Меняем поле:
current.title = 'Новый заголовок';

// Шлём всё назад:
const response = await fetch('/api/posts/42', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(current),
});

На сервере пост заменится целиком тем, что мы прислали. Поля bodyauthortags сохранятся — потому что мы их сами вложили в тело. Если бы мы их забыли — они исчезли бы (или приняли дефолтные значения).

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

Через PATCH — шлём только дельту

const response = await fetch('/api/posts/42', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Новый заголовок' }),
});

Короче, понятнее, эффективнее. Никаких лишних полей в теле, сервер сам разберётся, что обновить, а остальное оставить.

Это самый частый паттерн «поменять один чекбокс»: дёргаем PATCH с тем единственным полем, которое менялось. Никаких лишних round-trip-ов для подгрузки текущего состояния.

Когда выбрать какой

  • PUT — когда у вас на руках уже есть весь ресурс и логично его перезаписать. Например, форма «редактировать профиль» с двадцатью полями: пользователь много чего поменял, и логичнее отправить весь новый профиль одним сообщением.
  • PATCH — когда меняется одно-два поля. Переключатель «активно», обновление аватарки, переименование.

Если документация API явно говорит, какой метод использовать — следуйте ей, даже если кажется «неправильно». REST — конвенция, а не закон, и каждый сервис её интерпретирует немного по-своему.

JSON Patch — для перфекционистов

Стандарт RFC 6902 описывает специальный формат для PATCH-запросов — JSON Patch. Тело — массив операций:

PATCH /api/posts/42
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/title", "value": "Новый заголовок" },
  { "op": "add",     "path": "/tags/-", "value": "javascript" },
  { "op": "remove",  "path": "/author" }
]

Операции: addremovereplacecopymovetest. Позволяет описать сложные точечные изменения, в том числе во вложенных полях. Поддерживается далеко не каждым API — смотрите документацию. Если у API нет JSON Patch, шлите обычный JSON-объект с полями.

Что часто делает не так начинающий

  • На PUT шлёт только изменённые поля — и у пользователя «исчезает» имя или аватарка.
  • На PATCH шлёт весь объект (включая idcreated_at и прочую системщину) — сервер ругается на попытку поменять readonly-поля.
  • Использует POST вместо PATCH «потому что POST умеет всё». Технически — работает; стилистически — не REST.

Дальше — единственная в этом модуле глава про входящие заголовки: кэширование.