Когда вы открываете документацию к чужому API и видите эндпоинт /users/create, внутри что-то щелкает. Это не просто стиль, это сигнал о том, что разработчик думал о действии, а не о данных. Ресурсно-ориентированный подход в REST API is архитектурный стиль, где URI идентифицируют ресурсы (сущности), а HTTP-методы описывают операции над ними. Такой подход делает предсказуемым поведение системы для любого клиента, от мобильного приложения до интеграции с CRM.
Почему глаголы ломают логику
Представьте, что у вас есть сервис управления заказами. Если вы используете глаголы, список эндпоинтов превращается в меню действий:
/orders/get/orders/list/orders/update/orders/delete
Здесь проблема в масштабируемости. Что будет, если нам нужно получить заказы только с фильтром по статусу «оплачено»? Добавим новый глагол? /orders/getPaid? А если нужен экспорт в CSV? /orders/exportCsv. С каждым новым требованием URL становится все длиннее и менее стандартизировым.
В ресурсной модели мы смотрим на объект. Заказ - это ресурс. У него есть состояние (ID, сумма, статус). Чтобы получить список заказов, мы используем метод GET на коллекцию /orders. Чтобы изменить один, используем PATCH или PUT на конкретный ресурс /orders/{id}. Глагол уже встроен в протокол HTTP, поэтому повторять его в адресе избыточно и вредно для кэширования.
Базовые правила построения URI
Чтобы ваш API выглядел профессионально и легко читался, придерживайтесь нескольких жестких правил. Они помогают избежать типичных ошибок новичков.
- Используйте существительные во множественном числе. Не
/user, а/users. Не/order, а/orders. Это унифицирует работу с коллекциями. - Разделяйте уровни вложенности слешами. Если у пользователя есть адреса, правильный путь -
/users/{userId}/addresses. Избегайте точек в именах ресурсов, они зарезервированы для версий API (например,v1/users). - Версия в начале пути. Всегда начинайте с версии:
/v1/orders. Это позволяет менять структуру в будущем, не ломая старые клиенты. - Заглавные буквы только для аббревиатур. В остальном используйте нижний регистр (
snake_caseилиkebab-case). Например,/product-categoriesлучше, чем/ProductCategories.
HTTP-методы как замена глаголам
Самое сложное для многих бэкендеров - перестать думать «что сделать» и начать думать «что изменить». Вот базовое соответствие между действиями и методами:
| Действие | Метод | URI пример | Описание |
|---|---|---|---|
| Получить список | GET | /v1/products | Возвращает коллекцию ресурсов |
| Получить один элемент | GET | /v1/products/101 | Возвращает конкретный ресурс по ID |
| Создать новый | POST | /v1/products | Отправляет данные для создания, сервер генерирует ID |
| Обновить полностью | PUT | /v1/products/101 | Заменяет весь ресурс новыми данными |
| Обновить частично | PATCH | /v1/products/101 | Изменяет только переданные поля |
| Удалить | DELETE | /v1/products/101 | Удаляет ресурс (или помечает удаленным) |
Обратите внимание на разницу между PUT и PATCH. PUT идемпотентен: если вы отправите один и тот же запрос дважды, результат будет тем же самым. PATCH часто используется для точечных правок, например, изменения только статуса заказа. Если вы используете PUT для частичного обновления, клиент должен передавать все поля, иначе недостающие могут обнулиться.
Работа со связями и вложенными ресурсами
Часто возникает вопрос: как спроектировать связь «один ко многим»? Например, у компании много сотрудников. Есть два популярных подхода.
Первый вариант: глубокая вложенность. Путь выглядит так: /companies/{companyId}/employees. Это интуитивно понятно и хорошо работает, когда сотрудники имеют смысл только в контексте конкретной компании. Но если сотрудник может работать в двух местах или вы хотите получить список всех сотрудников системы, этот путь становится неудобным.
Второй вариант: плоская структура с фильтрами. Вы создаете отдельный ресурс /employees и добавляете параметр фильтрации: /employees?company_id=15. Этот подход более гибкий и масштабируемый. Он позволяет легко строить сложные запросы, такие как «все сотрудники, чья зарплата выше X и которые работают в компании Y».
Для большинства современных микросервисных архитектур второй вариант предпочтительнее. Он сохраняет независимость ресурсов и упрощает кэширование на уровне CDN или прокси-серверов.
Типичные ошибки и как их избежать
Даже опытные команды иногда допускают ошибки, которые портят эстетику и функциональность API. Вот три самых частых:
- Использование ID в корне без контекста. Путь
/items/5ничего не говорит о том, что это за предмет. Лучше/products/5. - Смешение сингулярной и плюральной форм. Если у вас есть
/users, то получение одного пользователя должно быть/users/1, а не/user/1. Непоследовательность заставляет клиентов гадать. - Игнорирование кодирования параметров. Если в названии товара есть пробелы или кириллица, обязательно используйте URL-encoding.
/search?q=iphone%20caseвместо/search?q=iphone case.
Также важно правильно обрабатывать ошибки. Вместо возврата пустого массива при отсутствии ресурса, возвращайте стандартный код состояния 404 Not Found. Это помогает клиентам быстро понять причину сбоя.
Практический пример: рефакторинг старого API
Допустим, у вас есть легаси-система с такими эндпоинтами:
POST /api/addUserGET /api/getUserProfile?id=5POST /api/changePassword
Как перевести это в ресурсную модель?
POST /api/addUserстановитсяPOST /v1/users. Данные пользователя передаются в теле запроса.GET /api/getUserProfile?id=5становитсяGET /v1/users/5. ID теперь часть пути, а не query-параметра.POST /api/changePassword- самый интересный случай. Пароль - это атрибут пользователя. Логичное решение:PATCH /v1/users/5/password. Мы создаем под-ресурс «password» внутри пользователя, чтобы явно указать, что меняем именно его, а не весь профиль.
Такой рефакторинг делает API предсказуемым. Любой разработчик, посмотрев на URL, сразу понимает, какая операция выполняется и над каким объектом.
Инструменты для проверки и документирования
Проектировать API на бумаге - одно, а проверить его работоспособность - другое. Для визуализации структуры и автоматической генерации документации используют спецификации. Самая популярная из них - OpenAPI (ранее Swagger). Она позволяет описать каждый эндпоинт, типы данных и возможные ошибки. Инструменты вроде Postman или Insomnia позволяют тестировать эти эндпоинты прямо из браузера, экономя время на написании скриптов для проверки.
Можно ли использовать глаголы в REST API?
Технически да, но это противоречит принципам ресурсно-ориентированного дизайна. Глаголы затрудняют кэширование и делают API менее предсказуемым. Исключение составляют нестандартные операции, которые нельзя выразить стандартными HTTP-методами, но таких случаев немного.
Какая разница между PUT и PATCH?
PUT заменяет ресурс целиком. Если вы не передали поле, оно считается отсутствующим (обнуляется). PATCH выполняет частичное обновление. Вы передаете только те поля, которые нужно изменить, остальные остаются нетронутыми. PUT идемпотентен, PATCH - не всегда.
Где указывать версию API: в URL или в заголовках?
В URL (например, /v1/) проще для отладки и совместимости с инструментами, которые не поддерживают кастомные заголовки. В заголовках (Accept: application/vnd.company.v1+json) чище архитектура, но сложнее настройка клиентов. Для публичных API чаще выбирают URL.
Стоит ли делать отдельные эндпоинты для каждого действия?
Нет. Старайтесь группировать действия вокруг ресурсов. Если действие тесно связано с состоянием объекта (например, изменение пароля), сделайте под-ресурс. Если действие глобальное (например, поиск по всей системе), можно использовать отдельный ресурс /search.
Как обрабатывать пагинацию в списках?
Используйте query-параметры ?page=1&limit=10. Это стандартный подход, который понимают большинство клиентов. Альтернатива - курсорная пагинация (?cursor=abc123), которая эффективнее для больших датасетов, так как не требует пересчета количества элементов.