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

Laravel backend для React SPA: как спроектировать бэкенд-архитектуру

Когда беру проект, где фронт делают на React, а бэкенд нужно поднять с нуля, почти всегда ставлю Laravel - не потому что модно, а потому что за годы работы с PHP-фреймворками именно он даёт минимум трения на связке с SPA. Продуманный laravel backend для react spa экономит недели на авторизации, структуре API и деплое: разница между “потом переделаем” и архитектурой, спроектированной на старте, - это обычно 2-3 недели рефакторинга где-то в середине проекта, когда бизнес-логика уже наросла и трогать основы страшно.

В этой статье разберу, как собираю такие бэкенды на практике: от структуры проекта и выбора способа авторизации до CORS, обработки ошибок и деплоя.

Архитектура Laravel API для React SPA: с чего начинать

Первое решение - монорепозиторий или два отдельных репо. Для небольших проектов (CRM на 5-10 сущностей, внутренний дашборд) держу фронт и бэк в одном репозитории: react-приложение собирается в public/build, Laravel отдаёт index.html через один catch-all роут. Для проектов побольше - SaaS, интернет-магазин с отдельной мобильной версией в будущем - сразу развожу на два репозитория и два поддомена: api.site.ru и app.site.ru. Так проще масштабировать команду и деплоить фронт и бэк независимо.

Laravel в этой связке работает как API-only: контроллеры лежат в app/Http/Controllers/Api, все ответы - только JSON, никаких blade-шаблонов кроме писем и, может быть, страницы для приёма вебхуков. С 11 версии есть команда php artisan install:api, которая сразу поднимает routes/api.php и накатывает Sanctum - раньше это приходилось настраивать руками минут 20-30.

С первого дня закладываю версионирование: /api/v1/… вместо голого /api/.… Кажется избыточным на старте, но когда через полгода нужно поменять формат ответа для мобильного приложения, не трогая веб-версию, это решение окупается один раз и навсегда.

Аутентификация в связке Laravel и React: Sanctum, Passport или JWT

Самый частый вопрос на старте проекта - как авторизовывать пользователя. Разбираю по вариантам, которые реально применял на практике.

Способ Когда использую Особенности
Sanctum (cookie-based) SPA и API на одном домене или соседних поддоменах Сессии Laravel, CSRF-защита из коробки, токен не хранится в localStorage
Sanctum (токены) Мобильные клиенты, сторонние интеграции, отдельные CLI-скрипты Personal access tokens, легко отозвать доступ конкретному клиенту
Passport (OAuth2) Нужен полноценный OAuth2 с третьими сторонами (партнёрские интеграции) Тяжелее в настройке, оправдан только при реальном OAuth2-флоу
JWT (tymon/jwt-auth и аналоги) Несколько независимых сервисов на разных стеках Требует отдельно продумывать инвалидацию и рефреш токена

Для большинства проектов - CRM-панели, админки, дашборды на React от 100 000 ₽ - беру Sanctum с cookie-based авторизацией: фронт и бэк на одном домене или api-поддомене, между ними stateful-соединение через сессии Laravel. Это проще в отладке и не тащит за собой историю с хранением токена в localStorage, которая уязвима для XSS.

Passport беру только когда заказчику реально нужен OAuth2 - например, чтобы партнёры подключались к API через собственные приложения. Для внутреннего SPA это избыточно почти всегда.

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

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

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

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

CORS, cookies и сессии: где чаще всего теряют часы

Половина вопросов от разработчиков, которые впервые собирают такую связку, - почему после логина фронт получает 401 на все последующие запросы. Причины всегда одни и те же: не настроен SANCTUM_STATEFUL_DOMAINS, не совпадает SESSION_DOMAIN, забыт withCredentials на фронте или supports_credentials не выставлен в true в конфиге CORS.

Рабочий набор настроек для связки на соседних поддоменах выглядит так:

SESSION_DOMAIN=.site.ru
SANCTUM_STATEFUL_DOMAINS=app.site.ru
SESSION_DRIVER=cookie
SESSION_SAME_SITE=lax

Если фронт и бэк живут на разных доменах без общего родителя (например, приложение на отдельном поддомене хостинг-провайдера и api.site.ru), SameSite=lax не сработает - придётся ставить none и обязательно поднимать оба сервиса на https, иначе браузер тихо режет cookie без внятной ошибки в консоли. На этом лично терял часа три на одном из первых SPA-проектов, пока не проверил заголовки ответа руками через curl.

Второй частый баг - запрос на /sanctum/csrf-cookie не делается перед логином, и первый POST падает с 419. На фронте это решается одним axios-интерцептором, который дергает csrf-cookie перед первым мутирующим запросом, если токена ещё нет в cookies.

Структура эндпоинтов, валидация и формат ошибок для фронта

React-приложение - не человек, который прочитает текст ошибки на странице, поэтому формат ответа API должен быть предсказуемым и одинаковым для всех эндпоинтов. Использую связку FormRequest, API Resource и единый обработчик исключений в app/Exceptions/Handler.php, который приводит любую ошибку к одному виду: {"message": "...", "errors": {...}}.

Валидацию всегда выношу в отдельные классы FormRequest, а не пишу проверки внутри контроллера - это не только чище, но и даёт фронту стабильный код 422 с массивом errors по полям, который удобно раскидать по инпутам формы через react-hook-form или Formik.

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

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

Интеграция бэкенда с внешними сервисами: эквайринг, доставка, очереди

Отдельный API редко живёт в вакууме - почти в каждом интернет-магазине или SaaS с оплатой бэкенду нужно дергать внешние сервисы: эквайринг Т‑Банка для приёма платежей, API СДЭК для расчёта доставки и печати этикеток, иногда - Telegram-бота на aiogram для уведомлений менеджеру о новом заказе.

Все такие интеграции держу за интерфейсами (например, PaymentGatewayInterface) и оборачиваю вызовы в очереди Laravel - синхронный вызов внешнего API прямо во время HTTP-запроса от React делает интерфейс подвисающим и повышает риск таймаута. Webhook от Т‑Банка о смене статуса платежа тоже обрабатываю через очередь: сначала быстро отвечаю 200, потом уже в фоне обновляю заказ и дергаю уведомление в Telegram.

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

Если проект небольшой и разработчик в команде впервые собирает такую связку, закладывайте на очереди и вебхуки отдельное время в оценке - это обычно недооценивают на 20-30% от реального объёма работы.

Деплой и окружение: разделяем фронт и бэк без нервов

На проде держу React-сборку и Laravel-API либо на одном сервере с разными поддоменами через nginx, либо React на статическом хостинге, а Laravel - на отдельном VPS. Второй вариант чаще выбираю для SaaS-продуктов: статику раздавать через CDN дешевле и быстрее, чем гонять её через PHP-FPM.

Для очередей и cron-задач (напоминания, синхронизация с СДЭК, рассылки) на сервере обязательно supervisor, который держит php artisan queue:work живым и перезапускает при падении. Без него любая очередь через пару дней тихо останавливается после деплоя или перезагрузки сервера, а найти это не сразу - заказы копятся в базе, а уведомления не уходят.

Отдельно развожу .env для локальной разработки, стейджа и прода - особенно значения APP_URL, SESSION_DOMAIN и ключи внешних сервисов. Смешение продовых и тестовых ключей эквайринга в одном .env - классическая ошибка, которая один раз чуть не привела к списанию тестовых платежей боевыми картами клиентов на одном из проектов.

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

API и серверная часть под SPA

API / Бэкенд

от 100 000 ₽

Подробнее →

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

Нужен ли Sanctum, если React и Laravel живут на разных доменах без общего родителя?

Да, но с оговоркой: cookie-based режим Sanctum рассчитан на общий родительский домен или явно перечисленные stateful-домены с SameSite=none и https на обеих сторонах. Если доменов действительно два независимых (например, фронт на своём поддомене хостинг-провайдера), проще и надёжнее выдавать персональные токены через Sanctum и передавать их в заголовке Authorization, а не городить cookie через разные домены.

Сколько по времени занимает разработка Laravel-бэкенда для React SPA?

Для CRM или админки среднего размера (10-15 сущностей, авторизация, роли, пара внешних интеграций) закладываю 3-5 недель. Если добавляются эквайринг, доставка и очереди с вебхуками - плюс 1-2 недели на тестирование edge-кейсов вроде повторных вебхуков или сбоя внешнего API. Стоимость такого бэкенда у меня начинается от 100 000 ₽.

Можно ли подключить Laravel API к уже готовому React-приложению, которое писал другой разработчик?

Можно, но перед этим смотрю, как фронт уже настроен на работу с бэкендом: какие заголовки он шлёт, как хранит токен, есть ли интерцепторы axios для рефреша сессии. Часто оказывается, что фронт писали под REST без учёта CSRF или cookie-флоу, и часть кода на клиенте приходится переписывать вместе с бэкендом, а не только добавлять API.

Какую версию Laravel брать для нового API-проекта в 2026 году?

Беру актуальную LTS-ветку на момент старта проекта - на новых проектах это упрощает установку Sanctum и структуру routes/api.php через install:api, плюс дольше будет поддержка безопасности без вынужденной миграции посреди проекта.

Есть задача?

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

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

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

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