Fullstack на JavaScript — API и фронтенд
Fullstack на JavaScript — API и фронтенд
Что такое fullstack в этой статье
Fullstack здесь — два приложения на JavaScript, которые работают вместе:
- Бэкенд (API) — Node + Express, порт
3000, отдаёт JSON (Первая программа на Node.js, Express — middleware, маршруты и ошибки). - Фронтенд (UI) — React, Vue или Next в браузере, рисует кнопки и списки (272, 282, Первая программа на Next.js).
Вы уже умеете поднимать каждую часть отдельно. На практике их запускают одновременно в двух терминалах — и сталкиваются с CORS и портами. Эта статья — карта склейки без магии.
Короткий roadmap по fullstack-связке
- Проверить API отдельно через
curl(чтобы исключить ошибки фронтенда). - Поднять UI и подключить
fetchк реальному endpoint. - Выбрать стратегию dev-интеграции: CORS или локальный прокси.
- Закрепить путь запроса в DevTools и убрать типовые ошибки портов.
Схема — кто с кем говорит
| Часть | Порт (пример) | Роль |
|---|---|---|
| API | 3000 | Данные, правила, позже БД |
| Vite (React/Vue) | 5173 | SPA: один HTML, дальше JS |
| Next.js | 3000 по умолчанию | Конфликт с API — сдвиньте API на 3001 или Next на 3001 |
Браузер выполняет JS фронта. JS вызывает fetch('http://127.0.0.1:3000/notes') — это отдельный HTTP-запрос с страницы на другой порт.
Шаг 1 — API должен отвечать без фронта
В каталоге API:
cd notes-api
npm run dev
Разбор:
cd notes-apiпереводит терминал в каталог backend-проекта.npm run devзапускает API в режиме разработки с авто-перезапуском.- На этом шаге важно убедиться, что сервер действительно слушает ожидаемый порт до запуска фронта.
Проверка во втором терминале (или в том же, если сервер в фоне):
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/notes
Разбор:
curlотправляет HTTP-запрос напрямую к API без браузерных ограничений./healthпроверяет, что приложение живо и роутинг работает./notesпроверяет бизнес-эндпоинт, который потом будет вызываться из UI.- Если здесь всё хорошо, проблемы на фронте чаще связаны с CORS, URL или прокси. Проверка в терминале — curl / fetch — API-запросы; типовые
fetchи axios с разбором POST, токена и CORS — Fetch / axios — типовые запросы.
Ожидаемо: JSON и код 200. Если Connection refused — сервер не слушает порт; фронт это не исправит. Вернитесь к Первая программа на Node.js.
Шаг 2 — origin и CORS
Origin = протокол + хост + порт, например http://localhost:5173.
Страница с origin 5173 запрашивает API на 3000 — для браузера это кросс-доменный запрос. Сервер должен явно разрешить его заголовками CORS.
В API:
npm install cors
Разбор:
- Установка
corsдобавляет middleware для корректных ответов браузеру. - Пакет нужен именно API-приложению, а не React/Vue-проекту.
- После установки конфиг применяется через
app.use(cors(...)).
import cors from 'cors';
app.use(
cors({
origin: ['http://127.0.0.1:5173', 'http://localhost:5173'],
methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
}),
);
Разбор:
originперечисляет разрешённые адреса фронтенда в dev-среде.methodsопределяет методы, доступные при preflight-проверке.app.use(...)распространяет политику на маршруты, подключённые после этой строки.- Корректная CORS-настройка решает кейс "curl работает, браузер блокирует".
Подробнее про цепочку middleware: Express — middleware, маршруты и ошибки. В production в origin — домен вашего сайта, не *, если нужны cookie.
Postman и curl CORS не проверяют. Ошибка "в консоли браузера CORS" при рабочем curl — нормальная ситуация для новичка и частый этап в первых fullstack-проектах.
Шаг 3 — fetch на клиенте
fetch — встроенный в браузер способ сделать HTTP-запрос. Возвращает Promise; ответ нужно проверить и распарсить:
const API = 'http://127.0.0.1:3000';
async function loadNotes() {
const res = await fetch(`${API}/notes`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const notes = await res.json();
return notes;
}
Разбор:
APIхранит базовый адрес сервера, чтобы не дублировать строку в каждом запросе.fetchсо шаблонной строкой (базаAPI+ путь/notes) отправляет GET на endpoint списка заметок.- Проверка
!res.okобрабатывает HTTP-ошибки до попытки парсить тело. await res.json()превращает JSON-ответ в объект/массив JavaScript.- Возврат
notesделает функцию переиспользуемой в разных компонентах UI.
| Часть выражения | Смысл |
|---|---|
шаблонная строка API + /notes | Склеить базовый URL и путь |
await fetch(...) | Дождаться ответа сервера |
res.ok | true, если статус 200–299 |
res.json() | Тело ответа как объект JavaScript |
В примере API слушает порт 3000 (http://127.0.0.1:3000), а страница в dev часто открыта на 5173 (Vite) — поэтому нужен CORS или прокси. В продакшене браузер ходит на 443 (HTTPS) к одному домену, а бэкенд может стоять за reverse proxy и слушать другой порт внутри сети — пример nginx proxy_pass; упаковка Node API и SPA в контейнеры — Dockerfile — 10 типовых образов. Справочник типовых служб и портов (DNS 53, HTTPS 443, PostgreSQL 5432) — Сетевые сервисы по ролям.
| Фреймворк | Где вызывать |
|---|---|
| React | useEffect при монтировании — 272; готовые блоки UI — React — компоненты-рецепты |
| Vue | onMounted — 282 |
| Next (client) | 'use client' + useEffect — Первая программа на Next.js |
| Next (server) | async Server Component: запрос идёт с сервера Next, CORS в браузере не участвует; API должен быть доступен с машины, где крутится Next |
Шаг 4 — прокси в dev (альтернатива CORS)
Идея — браузер ходит на тот же origin, что и страница (5173), а dev-сервер тихо пересылает запрос на API.
Vite (vite.config.js):
export default {
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
};
Разбор:
server.proxyв Vite перенаправляет локальные запросы без участия браузерного CORS.- Ключ
'/api'означает, что фронт вызывает относительные URL вида/api/.... targetуказывает реальный backend-адрес.rewriteудаляет префикс/api, чтобы backend получил свой нативный путь/notes.changeOriginпомогает совместимости с серверами, зависящими от заголовкаHost.
| Настройка | Зачем |
|---|---|
target | Куда слать запрос на самом деле |
rewrite | /api/notes → /notes на бэкенде |
changeOrigin | Подменить заголовок Host для некоторых серверов |
Во фронте:
const res = await fetch('/api/notes'); // origin остаётся :5173
Разбор:
- Запрос идёт на тот же origin, что и страница, поэтому браузер не применяет CORS-проверку.
- Dev-сервер прозрачно проксирует его на API по
target. - Такой стиль уменьшает количество "жёстко зашитых" URL в клиентском коде.
Next.js (next.config.ts):
const nextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://127.0.0.1:3000/:path*',
},
];
},
};
export default nextConfig;
Разбор:
rewrites()задаёт правило внутреннего проксирования в Next dev/prod-среде.source: '/api/:path*'перехватывает все пути с префиксом/api.destinationперенаправляет запрос на внешний API, сохраняя остаток пути.- Это удобный BFF-подход: фронт работает с единым относительным API-префиксом.
Прокси удобен в разработке; в production часто отдельный домен API + CORS или один домен за Nginx.
Два терминала — рабочий ритуал
Терминал 1: cd notes-api && npm run dev → слушает :3000
Терминал 2: cd notes-ui && npm run dev → открыть :5173 в браузере
Разбор:
- Разделение по терминалам даёт независимый lifecycle для API и UI.
- Если один процесс падает, второй продолжает работать, что упрощает диагностику.
- Явная фиксация портов помогает быстрее находить конфликты и ошибки URL.
В Chrome: F12 → Network → обновить страницу → кликнуть запрос notes → вкладка Preview: массив JSON, статус 200.
Если статус (failed) или CORS — API, cors или прокси; если 404 — неверный путь или rewrite.
POST и DELETE с фронта
Создание заметки:
await fetch(`${API}/notes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: 'новая заметка' }),
});
Разбор:
method: 'POST'переключает запрос на создание ресурса.Content-Type: application/jsonсообщает серверу формат тела.JSON.stringify(...)сериализует JS-объект в строку для HTTP-передачи.- Шаблон "база
API+/notes" сохраняет единый источник правды для базового URL.
| Поле | Зачем |
|---|---|
method: 'POST' | HTTP-метод "создать" |
Content-Type | Сервер знает, что тело — JSON (express.json() на бэке) |
body: JSON.stringify(...) | Объект JS → строка для провода |
Удаление:
await fetch(`${API}/notes/1`, { method: 'DELETE' });
Разбор:
- DELETE-запрос обращается к конкретному ресурсу по идентификатору в URL.
- Тело не требуется: серверу достаточно метода и пути.
- После успешного ответа UI должен синхронизировать локальное состояние списка.
После POST обновите список в state: снова вызовите loadNotes() или добавьте заметку в массив вручную — иначе UI останется со старыми данными.
Частые ошибки
| Симптом | Причина | Решение |
|---|---|---|
Failed to fetch | API выключен или неверный URL | curl /health, проверить терминал 1 |
| CORS в консоли | Нет cors или другой origin | Express — middleware, маршруты и ошибки или прокси Vite |
ECONNREFUSED | Порт/хост не тот | Везде 127.0.0.1 или везде localhost |
| Next и API оба на 3000 | Конфликт портов | API → PORT=3001 |
| Пустой массив, 200 | In-memory store сбросился при перезапуске API | Ожидаемо для Первая программа на Node.js |
404 на /api/notes | Прокси без rewrite | Путь на бэке — /notes, не /api/notes |
Что попробовать
- Один API — два UI (React и Vue) с одного
originвcors. - Route Handler в Next, который проксирует на Node: секреты API остаются на сервере.
- Тот же сценарий "Заметки" на Python: Flask 3411.
Связанные материалы
| Тема | Материал |
|---|---|
| Express углубление | Express — middleware, маршруты и ошибки |
| React / Vue / Next | 272 · 282 · Первая программа на Next.js · галерея React-компонентов |
| Тесты API | Тестирование JavaScript — Vitest и Testing Library |
| Чек-лист | JavaScript — чек-лист |
Fullstack-поток запроса — от кнопки до ответа API
Чтобы быстрее диагностировать ошибки, полезно держать в голове полный путь одного действия пользователя.
Жизненный цикл одного действия
- Пользователь нажимает кнопку в интерфейсе.
- Фронтенд отправляет
fetchс JSON-телом. - API валидирует данные и выполняет логику.
- API возвращает статус и JSON-ответ.
- Фронтенд обновляет state и перерисовывает UI.
Где обычно ломается связка
| Симптом | Что проверить первым |
|---|---|
В браузере ошибка, а curl успешен | CORS / origin |
404 только во фронте | Прокси и rewrite |
| POST успешен, но UI старый | Локальный state не обновляется |
ECONNREFUSED | Неверный хост или порт |
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.