Принципы REST

REST — стиль проектирования API: ресурсы, единый набор методов, предсказуемые url.
«REST — это договорённость: ресурсы — существительные, методы HTTP — глаголы над ними.»

До этого мы делали отдельные маршруты как придётся. REST — это набор соглашений, который превращает хаос эндпоинтов в стройную систему. Освоив его, ты будешь проектировать API, понятные любому разработчику с первого взгляда. В этом уроке разберём принципы REST и спроектируем ресурс на их основе.

Ресурсы и методы

В REST всё крутится вокруг ресурсов — сущностей вроде пользователей, статей, заказов. Url именует ресурс (существительное), а HTTP-метод задаёт действие над ним (глагол). Не нужно придумывать /getUser или /deleteUser — действие уже в методе.

Метод и путьДействие
GET /usersсписок пользователей
GET /users/:idодин пользователь
POST /usersсоздать пользователя
PUT /users/:idзаменить целиком
PATCH /users/:idобновить частично
DELETE /users/:idудалить

Карта REST-маршрутизации

             /users
   GET ----------------> список
   POST ---------------> создать (201)

             /users/:id
   GET ----------------> получить один (или 404)
   PUT ----------------> заменить
   PATCH --------------> частично обновить
   DELETE -------------> удалить (204)

Сопоставление запроса и действия

Смоделируем ядро REST-роутера в браузере: по методу и шаблону пути он выбирает действие. Это объясняет, как Express различает шесть операций над одним ресурсом:

function resolve(method, path) {
  const isCollection = path === '/users';
  const itemMatch = path.match(/^\/users\/(\w+)$/);

  if (isCollection && method === 'GET')  return 'список';
  if (isCollection && method === 'POST') return 'создать';
  if (itemMatch && method === 'GET')     return 'получить #' + itemMatch[1];
  if (itemMatch && method === 'DELETE')  return 'удалить #' + itemMatch[1];
  return '405 метод не поддержан';
}

console.log(resolve('GET', '/users'));      // список
console.log(resolve('POST', '/users'));     // создать
console.log(resolve('GET', '/users/42'));   // получить #42
console.log(resolve('DELETE', '/users/42')); // удалить #42

Как работает под капотом

REST опирается на семантику HTTP: GET безопасен и кэшируем, PUT и DELETE идемпотентны (повтор не меняет результат), POST — нет. Эти свойства не магия Express, а соглашения, которые ты сам должен соблюдать. Если их нарушить (например, менять данные через GET), сломаются кэширование, повторные запросы и ожидания клиентов.

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

  • Глаголы в url. /createUser вместо POST /users — это не REST. Действие уже в методе.
  • Несогласованные имена. То /user, то /users. Выбери множественное число и держись его.
  • Игнорировать идемпотентность. PUT и DELETE должны давать одинаковый результат при повторе.

Best practices

  • Именуй коллекции существительными во множественном числе: /users, /orders.
  • Вложенность для связей: /users/:id/orders — заказы конкретного пользователя.
  • Возвращай статусы по смыслу: 201 при создании, 204 при удалении, 404 при отсутствии.

Итоги

REST — это соглашения: ресурсы-существительные в url и методы-глаголы HTTP. Они делают API предсказуемым. Дальше реализуем полный CRUD-набор маршрутов для ресурса и наведём порядок с помощью Router.

Версионирование и формат ответа

Реальное API живёт годами и меняется, поэтому к принципам REST добавляют два практических соглашения. Первое — версионирование: контракт фиксируют в пути (/v1/users) или в заголовке, чтобы новые изменения не ломали старых клиентов. Второе — единый формат ответа: и успех, и ошибка приходят предсказуемой структурой, например { "data": ... } или { "error": ... }, чтобы фронтенду не приходилось угадывать форму. Эти договорённости не входят в формальное определение REST, но именно они отличают учебный API от того, который не стыдно отдать в эксплуатацию другой команде.

Проверьте себя
1. Как по-REST назвать создание пользователя?
AGET /createUser
BPOST /users
CPOST /createUser
DGET /users/new
2. Какое свойство означает идемпотентность PUT?
AЗапрос выполняется быстрее
BПовторный одинаковый запрос даёт тот же результат
CОтвет всегда кэшируется
DPUT создаёт новый ресурс при каждом вызове