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

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

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

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

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

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

  1. Бэкенд (API) — Node + Express, порт 3000, отдаёт JSON (262, 263).
  2. Фронтенд (UI) — React, Vue или Next в браузере, рисует кнопки и списки (272, 282, 2731).

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


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

ЧастьПорт (пример)Роль
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

Проверка во втором терминале (или в том же, если сервер в фоне):

curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/notes

Ожидаемо: JSON и код 200. Если Connection refused — сервер не слушает порт; фронт это не исправит. Вернитесь к 262.


Шаг 2 — origin и CORS

Origin = протокол + хост + порт, например http://localhost:5173.

Страница с origin 5173 запрашивает API на 3000 — для браузера это кросс-доменный запрос. Сервер должен явно разрешить его заголовками CORS.

В API:

npm install cors
import cors from 'cors';

app.use(
cors({
origin: ['http://127.0.0.1:5173', 'http://localhost:5173'],
methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
}),
);

Подробнее про цепочку middleware: 263.md. В production в origin — домен вашего сайта, не *, если нужны cookie.

Важно: Postman и curl CORS не проверяют. Ошибка «в консоли браузера CORS» при рабочем curl — нормальная ситуация для новичка.


Шаг 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}/notes`Шаблонная строка: склеить базовый URL и путь
await fetch(...)Дождаться ответа сервера
res.oktrue, если статус 200–299
res.json()Тело ответа как объект JavaScript
ФреймворкГде вызывать
ReactuseEffect при монтировании — 272
VueonMounted282
Next (client)'use client' + useEffect2731
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/, ''),
},
},
},
};
НастройкаЗачем
targetКуда слать запрос на самом деле
rewrite/api/notes/notes на бэкенде
changeOriginПодменить заголовок Host для некоторых серверов

Во фронте:

const res = await fetch('/api/notes'); // origin остаётся :5173

Next.js (next.config.ts):

const nextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://127.0.0.1:3000/:path*',
},
];
},
};
export default nextConfig;

Прокси удобен в разработке; в production часто отдельный домен API + CORS или один домен за Nginx.


Два терминала — рабочий ритуал

Терминал 1: cd notes-api && npm run dev → слушает :3000
Терминал 2: cd notes-ui && npm run dev → открыть :5173 в браузере

В 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'HTTP-метод «создать»
Content-TypeСервер знает, что тело — JSON (express.json() на бэке)
body: JSON.stringify(...)Объект JS → строка для провода

Удаление:

await fetch(`${API}/notes/1`, { method: 'DELETE' });

После POST обновите список в state: снова вызовите loadNotes() или добавьте заметку в массив вручную — иначе UI останется со старыми данными.


Частые ошибки

СимптомПричинаРешение
Failed to fetchAPI выключен или неверный URLcurl /health, проверить терминал 1
CORS в консолиНет cors или другой origin263 или прокси Vite
ECONNREFUSEDПорт/хост не тотВезде 127.0.0.1 или везде localhost
Next и API оба на 3000Конфликт портовAPI → PORT=3001
Пустой массив, 200In-memory store сбросился при перезапуске APIОжидаемо для 262
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 углубление263.md
React / Vue / Next272 · 282 · 2731
Тесты API33.md
Чек-лист999.md

См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).