За практику разработки бэкендов я не раз переделывал 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 ₽.