Разработка · 6 мин чтения

REST API для приложения: как спроектировать без переделок

За практику разработки бэкендов я не раз переделывал API, спроектированный на живую - без ресурсной модели, без версий, с разными форматами ошибок в разных контроллерах. Когда бизнес-логика раскладывается по HTTP-методам и путям заранее, rest api для приложения живёт годами и спокойно обрастает новыми клиентами: мобильным приложением, ботом в Telegram, интеграцией с CRM. Ниже - план, по которому я сам веду проектирование, от ресурсов до документации, с конкретными цифрами и примерами из реальных проектов.

Ресурсная модель - с чего начинается REST API для приложения

Первая ошибка, которую вижу почти в каждом legacy-бэкенде - эндпоинты называют глаголами: /getOrders, /createUser, /updateProfile. REST строится на существительных и HTTP-методах, а не на названиях функций. Ресурс - это сущность бизнеса: заказ, пользователь, товар, платёж. Метод (GET, POST, PUT, PATCH, DELETE) уже описывает действие само по себе.

Перед тем как писать первый контроллер, выписываю все сущности будущего приложения и связи между ними - обычно это час-полтора работы с карандашом, но экономит недели переделок. Для интернет-магазина на связке фронта и WooCommerce-бэкенда список выглядит так:

Ресурс Эндпоинт Методы
Заказы /api/v1/orders GET, POST, GET /{id}, PATCH /{id}
Товары /api/v1/products GET, GET /{id}
Пользователи /api/v1/users GET /{id}, PATCH /{id}
Платежи /api/v1/payments POST, GET /{id}

Вложенность оставляю не глубже двух уровней: /orders/{id}/items читается нормально, а /users/{id}/orders/{id}/items/{id}/comments заставляет клиента городить костыли для сборки URL. Если сущность самостоятельная - даю ей отдельный корневой путь, а id родителя передаю параметром запроса.

Версионирование и структура URL бэкенда

Версию закладываю в архитектуру с первого коммита, даже если планируется единственный клиент. Через полгода почти всегда появляется второй - мобильное приложение, виджет для Tilda или сценарий в n8n, которому нужен старый формат ответа, пока фронт не обновился.

На практике работают два подхода:

Способ Пример Когда использую
URL-версия /api/v1/orders Публичные API, много внешних клиентов
Header-версия Accept: application/vnd.app.v1+json Внутренние API с частыми правками

Для большинства проектов беру URL-версию - она видна в логах, легко тестируется в Postman и не требует объяснять партнёрам, как передавать заголовок:

# v1 — старый формат ответа, ещё используется ботом на aiogram
curl https://api.example.ru/api/v1/orders/482

# v2 — новый формат, добавили пагинацию и вложенные items
curl https://api.example.ru/api/v2/orders/482

Когда меняю формат ответа - не трогаю v1, а веду параллельно v2 и даю клиентам официальный срок на переход, обычно 2-3 месяца. Это дешевле, чем экстренно чинить сломавшийся мобильный клиент в сторе.

Аутентификация и авторизация в API веб-приложения

Смешивать несколько схем авторизации в одном API - вторая по частоте причина, по которой меня зовут переделывать бэкенд. Обычно раскладываю так:

Схема Кому подходит Особенность
JWT (access + refresh) Веб- и мобильные клиенты с логином Access живёт 15-30 минут, refresh - до 30 дней
OAuth2 Интеграции со сторонними сервисами Нужен, если внешние разработчики подключаются к вашему API
API-ключ Серверные интеграции: n8n, боты, вебхуки СДЭК Ключ привязан к IP или домену, легко отозвать

Для приложений с личным кабинетом ставлю JWT: access-токен с коротким сроком жизни в заголовке Authorization, refresh - в httpOnly cookie, чтобы не тянуть его в JS. Для серверных сценариев, когда n8n дёргает мой API по расписанию или бот на aiogram обновляет статус заказа, JWT избыточен - там ставлю статичный API-ключ с ограничением по IP и отдельным rate-limit на каждый ключ.

Права разграничиваю на уровне ресурса, не эндпоинта целиком: пользователь получает через GET /orders только свои записи, поле user_id для фильтра подставляется на бэкенде из токена, а не берётся из query-параметра - иначе на выходе получаете уязвимость, когда чужой заказ смотрят по подставленному id.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Формат ответов, коды ошибок и пагинация

Единый конверт ответа экономит часы на фронтенде - не нужно на каждый эндпоинт писать свою обработку ошибок. Использую такую структуру:

{
  "data": { "id": 482, "status": "paid" },
  "meta": { "request_id": "8f3a-21" },
  "error": null
}

А ошибку возвращаю в том же конверте, но с заполненным полем error и пустым data:

{
  "data": null,
  "meta": { "request_id": "8f3a-22" },
  "error": { "code": "validation_failed", "field": "email" }
}

Коды статусов держу предсказуемыми - фронтенд-команда должна заранее знать, чего ждать от каждого эндпоинта:

Код Значение Когда возвращаю
200 OK Успешный GET/PATCH
201 Created Успешный POST, ресурс создан
204 No Content Успешный DELETE
400 Bad Request Невалидный JSON, нет обязательного поля
401 Unauthorized Токен не передан или истёк
403 Forbidden Токен валиден, но прав нет
404 Not Found Ресурс не существует
409 Conflict Дублирующийся заказ, гонка при оплате
422 Unprocessable Entity Формат верный, бизнес-правила нарушены
429 Too Many Requests Превышен rate-limit
500 Internal Server Error Ошибка на сервере, логирую и алерчу

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

Документирование: OpenAPI и контракт для интеграций

Спецификацию OpenAPI завожу до того, как написан первый контроллер - описываю контракт, синхронизирую его с заказчиком, и только потом кодирую. Это особенно окупается, когда API отдаёт данные не только своему фронту, а ещё эквайрингу вроде Т‑Банка, СДЭК-трекингу заказов или веб-хуку в n8n: сторонняя система читает контракт, а не гадает по логам продакшена.

Swagger UI поднимаю прямо на бэкенде по отдельному пути - у команды и заказчика всегда актуальная песочница, где можно потыкать запросы без Postman-коллекции, которая рано или поздно устаревает. Для интеграций с СДЭК или платёжными шлюзами держу под рукой готовые обвязки - часть таких скриптов уже выложена в библиотеке готовых скриптов, что экономит день-два на рутинной части подключения.

Типичные ошибки, из-за которых REST API потом переделывают

  • Бизнес-логика в контроллере. Валидация, расчёт скидки и отправка письма в одном методе - тестировать это невозможно, а любое изменение задевает три вещи сразу вместо одной.
  • Отсутствие версии в самом начале. Кажется лишним для MVP, но добавить версионирование постфактум значит переписать все клиентские интеграции разом.
  • Разные форматы ошибок в разных контроллерах. Фронтенд обрастает условиями под каждый эндпоинт, а любая новая фича на бэке тут же ломает чей-то обработчик ошибок.
  • Пагинация «потом добавим». Список без пагинации нормально живёт до первой тысячи записей, а дальше отдаёт клиенту мегабайты JSON и кладёт мобильное приложение по таймауту.
  • Один токен на все сценарии. Личный кабинет и серверная интеграция с разными требованиями к безопасности сидят на одной схеме авторизации - отозвать доступ одному боту, не разлогинив всех пользователей, невозможно.

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

Когда нужен не сайт, а сервис

SaaS / SPA

от 300 000 ₽

Подробнее →

Частые вопросы

Сколько времени занимает проектирование REST API для приложения?

Для типового веб-сервиса с 5-8 сущностями закладываю 3-5 рабочих дней: ресурсная модель, черновик OpenAPI-спецификации, согласование с заказчиком или фронтенд-командой. Для проектов с внешними интеграциями - эквайринг, СДЭК, сторонние CRM - добавляю ещё 2-3 дня на проработку контрактов с каждой стороной.

REST или GraphQL - что выбрать для нового приложения?

Если у приложения один-два клиента с похожими требованиями к данным, беру REST - он проще в кешировании, логировании и отладке через обычный curl. GraphQL оправдан, когда клиентов много и у каждого свой набор полей: мобильное приложение и админка тянут разный объём данных с одного графа. На практике для большинства проектов, что ко мне приходят, хватает REST с грамотной пагинацией и фильтрами.

Нужна ли документация API, если сейчас с ним работает один разработчик?

Да, потому что документация нужна в первую очередь вам же через полгода, когда детали контроллеров забудутся. OpenAPI-спецификация ещё и генерирует Postman-коллекцию и типы для фронта автоматически - экономия времени начинается сразу, а не когда в проект придёт второй разработчик.

Сколько стоит разработка REST API под ключ?

У меня разработка API и бэкенда на Laravel начинается от 100 000 ₽ - сумма зависит от количества сущностей, схемы авторизации и внешних интеграций. Если нужен не бэкенд отдельно, а полноценный веб-сервис или SaaS с фронтом, точка входа - от 150 000 ₽. Консультация по архитектуре конкретного проекта - от 3 000 ₽.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно