Перейти к основному содержимому

Fullstack на JavaScript — API и фронтенд

Разработчику

Fullstack на JavaScript — API и фронтенд

Что такое fullstack в этой статье

Fullstack здесь — два приложения на JavaScript, которые работают вместе:

  1. Бэкенд (API) — Node + Express, порт 3000, отдаёт JSON (Первая программа на Node.js, Express — middleware, маршруты и ошибки).
  2. Фронтенд (UI) — React, Vue или Next в браузере, рисует кнопки и списки (272, 282, Первая программа на Next.js).

Вы уже умеете поднимать каждую часть отдельно. На практике их запускают одновременно в двух терминалах — и сталкиваются с CORS и портами. Эта статья — карта склейки без магии.

Короткий roadmap по fullstack-связке

  1. Проверить API отдельно через curl (чтобы исключить ошибки фронтенда).
  2. Поднять UI и подключить fetch к реальному endpoint.
  3. Выбрать стратегию dev-интеграции: CORS или локальный прокси.
  4. Закрепить путь запроса в DevTools и убрать типовые ошибки портов.

Схема — кто с кем говорит

ЧастьПорт (пример)Роль
API3000Данные, правила, позже БД
Vite (React/Vue)5173SPA: один HTML, дальше JS
Next.js3000 по умолчаниюКонфликт с 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.oktrue, если статус 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) — Сетевые сервисы по ролям.

ФреймворкГде вызывать
ReactuseEffect при монтировании — 272; готовые блоки UI — React — компоненты-рецепты
VueonMounted282
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 fetchAPI выключен или неверный URLcurl /health, проверить терминал 1
CORS в консолиНет cors или другой originExpress — middleware, маршруты и ошибки или прокси Vite
ECONNREFUSEDПорт/хост не тотВезде 127.0.0.1 или везде localhost
Next и API оба на 3000Конфликт портовAPI → PORT=3001
Пустой массив, 200In-memory store сбросился при перезапуске APIОжидаемо для Первая программа на Node.js
404 на /api/notesПрокси без rewriteПуть на бэке — /notes, не /api/notes

Что попробовать

  1. Один API — два UI (React и Vue) с одного origin в cors.
  2. Route Handler в Next, который проксирует на Node: секреты API остаются на сервере.
  3. Тот же сценарий "Заметки" на Python: Flask 3411.

Связанные материалы

ТемаМатериал
Express углублениеExpress — middleware, маршруты и ошибки
React / Vue / Next272 · 282 · Первая программа на Next.js · галерея React-компонентов
Тесты APIТестирование JavaScript — Vitest и Testing Library
Чек-листJavaScript — чек-лист

Fullstack-поток запроса — от кнопки до ответа API

Чтобы быстрее диагностировать ошибки, полезно держать в голове полный путь одного действия пользователя.


Жизненный цикл одного действия

  1. Пользователь нажимает кнопку в интерфейсе.
  2. Фронтенд отправляет fetch с JSON-телом.
  3. API валидирует данные и выполняет логику.
  4. API возвращает статус и JSON-ответ.
  5. Фронтенд обновляет state и перерисовывает UI.

Где обычно ломается связка

СимптомЧто проверить первым
В браузере ошибка, а curl успешенCORS / origin
404 только во фронтеПрокси и rewrite
POST успешен, но UI старыйЛокальный state не обновляется
ECONNREFUSEDНеверный хост или порт

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.