REST API на Laravel для мобильного приложения я делаю чаще всего под связку iOS/Android или Flutter/React Native на фронте, и это одна из самых благодарных задач в бэкенд-разработке - фреймворк даёт готовую инфраструктуру для аутентификации, валидации и работы с базой, а мне остаётся спроектировать эндпоинты под конкретный кейс. За последние пару лет я собрал больше десятка таких бэкендов - от простого MVP на 15 роутов до сервиса с оплатой через эквайринг Т‑Банка и трекингом доставки СДЭК, и в этой статье разберу, как выстраиваю такой API от каркаса до продакшена.
Почему для мобильного бэкенда беру именно Laravel
Мобильному приложению бэкенд нужен предсказуемый - без сюрпризов с сериализацией дат и без ручного написания мидлвара для каждой мелочи. В Laravel из коробки есть Eloquent ORM, миграции, очереди, кэш и Sanctum для токенной аутентификации - это закрывает большую часть типовых задач мобильного API без сторонних пакетов.
На практике сравниваю это с голым Node/Express: там ту же связку валидации, ORM и токенов приходится собирать руками из трёх-четырёх библиотек и следить, чтобы они не конфликтовали между собой. В Laravel всё это уже подружено и обновляется синхронно с релизами фреймворка.
Ещё один довод - Artisan. Генерация контроллеров, ресурсов, форм-реквестов и миграций одной командой экономит день-два на каждом проекте, особенно когда API растёт до 40-50 эндпоинтов.
Как проектирую структуру REST API на Laravel
Начинаю всегда с routes/api.php и группировки по версиям - даже если мобильное приложение пока одно, версионирование сразу закладываю на уровне префикса /api/v1, потому что вторая версия приложения появляется быстрее, чем кажется на старте проекта.
Дальше - ресурсные контроллеры через apiResource, никакой ручной раздачи роутов на каждый CRUD-метод:
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::apiResource('orders', OrderController::class);
Route::get('/user', function (Request $request) {
return $request->user();
});
});
Для мобильного клиента важно, чтобы каждый эндпоинт возвращал предсказуемый JSON независимо от того, вызван он из приложения или из Postman на этапе тестирования. Для этого использую API Resources - отдельный слой между моделью Eloquent и ответом API:
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'status' => $this->status,
'total' => $this->total,
'items' => OrderItemResource::collection($this->items),
'created_at' => $this->created_at->toIso8601String(),
];
}
}
Разделяю Resource и модель осознанно - так поле created_at я привожу к ISO 8601 один раз в одном месте, а не разбираюсь потом, почему в iOS-приложении дата парсится нормально, а в Android падает с ошибкой формата.
Sanctum или Passport - что выбираю для аутентификации в мобильном приложении
Для большинства мобильных бэкендов беру Sanctum - он выдаёт персональный токен на устройство, не требует OAuth2-хендшейка и настраивается за пару часов. Passport оставляю для кейсов, когда сторонние сервисы должны получать доступ к API от имени пользователя по OAuth2 - например, когда партнёрская интеграция заходит через ваш API.
| Критерий | Sanctum | Passport |
|---|---|---|
| Тип токенов | Personal Access Token | OAuth2 (Bearer, refresh) |
| Сложность настройки | Низкая, 1-2 часа | Выше, нужен OAuth-клиент и миграции |
| Подходит для | Своё мобильное или SPA-приложение | Сторонние интеграции, партнёрские API |
| Отзыв токена | Удаление записи в БД | Через refresh-токены и scopes |
На мобильном клиенте логика простая: логин отдаёт токен, токен кладётся в Keychain или Keystore, дальше идёт в заголовке Authorization: Bearer. Ротацию токенов делаю через отдельный эндпоинт /refresh, а не через долгоживущий токен без срока - иначе при утере телефона придётся вручную чистить токены в базе сразу для всех пользователей.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Валидация запросов и единый формат ошибок
Мобильное приложение не должно парсить HTML-страницу с ошибкой 500 - оно ждёт JSON строго определённой структуры. Поэтому первым делом переопределяю рендер исключений в app/Exceptions/Handler.php (или в bootstrap/app.php начиная с Laravel 11), чтобы любая ошибка возвращалась в едином формате: код, сообщение, детали по полям.
Валидацию выношу в Form Request классы - контроллер остаётся тонким, а правила переиспользуются между эндпоинтами создания и обновления записи.
public function render($request, Throwable $exception)
{
if ($request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage(),
'errors' => method_exists($exception, 'errors') ? $exception->errors() : null,
], $this->isHttpException($exception) ? $exception->getStatusCode() : 500);
}
return parent::render($request, $exception);
}
Продакшен: кэш, очереди и лимиты запросов
Как только API уходит в продакшен, три вещи спасают от падений под нагрузкой из мобильного приложения: Redis-кэш для справочников и каталога, очереди для всего, что не обязано отвечать мгновенно (push-уведомления, отправка чека, синхронизация статуса заказа с СДЭК), и throttle-мидлвар на каждый роут.
Лимит запросов настраиваю не глобально, а по группам - авторизационные эндпоинты 5-10 запросов в минуту с одного IP, чтение каталога - 60-100, потому что мобильное приложение может дёргать список товаров при каждом открытии экрана.
Отдельно слежу за N+1 запросами - на мобильном API это ощущается сильнее, чем на вебе, потому что один экран приложения часто дёргает 3-4 связанные сущности разом. Eager loading через with() в контроллере убирает большую часть таких проблем ещё на этапе разработки.
Если после релиза нужно перекладывать события заказа в CRM или отправлять уведомление в чат без написания отдельного сервиса, обычно подключаю автоматизацию в n8n - вебхук из Laravel улетает в сценарий, а дальше n8n сам раскладывает данные по нужным системам.
Тестирование и документация перед релизом
Feature-тесты в Laravel пишу для каждого эндпоинта, который трогает деньги, авторизацию или пользовательские данные - остальное покрываю по необходимости. PHPUnit с RefreshDatabase гоняет тесты за секунды, и это тот минимум, без которого я не отдаю API мобильной команде на интеграцию.
Документацию собираю через Scribe - он парсит Form Request и Resource классы и генерирует читаемую OpenAPI-спецификацию без ручного дублирования описаний. Мобильным разработчикам отдаю готовую Postman-коллекцию, чтобы не тратить день на созвоны с объяснением, что означает каждое поле в ответе.
API и серверная часть под SPA
API / Бэкенд
от 100 000 ₽
Подробнее →Частые вопросы
Сколько времени занимает разработка REST API на Laravel для мобильного приложения?
Простой MVP на 10-15 эндпоинтов без сложной бизнес-логики закрываю за 2-3 недели. Бэкенд с оплатой, push-уведомлениями и синхронизацией доставки занимает 4-6 недель. Разработка бэкенда на Laravel у меня начинается от 100 000 ₽ - точная сумма зависит от количества сущностей и интеграций.
Чем Sanctum лучше Passport для мобильного приложения?
Sanctum проще и быстрее в настройке, потому что не требует полного OAuth2-цикла - выдаёт токен на устройство и всё. Passport оправдан, только если сторонние сервисы должны получать доступ к вашему API от имени пользователя по OAuth2, а для большинства мобильных приложений это не требуется.
Нужна ли отдельная документация API для мобильной команды?
Да, и лучше генерировать её из кода, а не писать вручную в вики, которая устареет через неделю. Scribe или L5-Swagger снимают необходимость дублировать описание полей в двух местах - спецификация обновляется вместе с кодом.
Как тестировать REST API на Laravel перед релизом мобильного приложения?
Пишу Feature-тесты на PHPUnit для критичных сценариев - регистрация, оплата, изменение статуса заказа, и прогоняю их в CI перед каждым деплоем. Дополнительно собираю Postman-коллекцию с реальными запросами, чтобы мобильная команда могла тестировать эндпоинты руками ещё до того, как разработка интерфейса дойдёт до конкретного экрана.