Server routes и Nitro: бэкенд внутри Nuxt

Папка server/api превращает Nuxt в фулстек: файл становится эндпоинтом, а движок Nitro исполняет его как бэкенд — без отдельного сервера.
Суть: файлы в server/api/ автоматически становятся API-маршрутами с префиксом /api. Обработчик оборачивается в defineEventHandler, возвращённый объект сериализуется в JSON. Этот код исполняет движок Nitro и никогда не отдаёт в браузер.

До сих пор мы дёргали /api/..., как будто бэкенд уже существует. Пора его написать — прямо внутри Nuxt. Это и делает Nuxt фулстек-фреймворком: серверная часть живёт в том же проекте, что и фронтенд, и работает на движке Nitro.

Правило файловой маршрутизации работает и здесь. Файл в server/api/ становится эндпоинтом с префиксом /api. server/api/hello.ts отвечает на /api/hello. Обработчик оборачивают в defineEventHandler, а то, что вы вернёте, Nitro сам превратит в JSON:

// server/api/products.ts
export default defineEventHandler((event) => {
  return [
    { id: 1, name: "Книга", price: 500 },
    { id: 2, name: "Ручка", price: 50 },
  ]
})

Теперь useFetch("/api/products") на странице получит этот массив. Здесь, на сервере, можно безопасно обращаться к базе данных, читать секретные ключи из переменных окружения, ходить во внешние API — ничего из этого не попадёт в браузер. Это принципиально: клиентский код видят все, серверный — никто.

   Полный фулстек-цикл

   Страница: useFetch("/api/products")
        |
        v
   Nitro находит server/api/products.ts
        |
        v
   defineEventHandler выполняется (БД, секреты — здесь)
        |
        v
   возвращённый объект -> JSON -> страница

Грамотная обработка состояний — это ещё и про восприятие скорости. Пустой белый экран в ожидании данных ощущается медленнее, чем тот же интервал, но со скелетон-заглушкой, повторяющей будущую разметку. Поэтому ветку pending полезно оформлять не текстом «Загрузка...», а скелетоном карточек или строк. А ветку error стоит делать действенной: не просто «что-то пошло не так», а кнопку «Повторить», вызывающую refresh(). Эти мелочи отличают черновой прототип от продукта, которым приятно пользоваться даже на нестабильной сети.

Как работает под капотом

Nitro — это серверный движок Nuxt, построенный на библиотеке h3. При сборке он компилирует папку server/ в обработчики и поднимает их по соответствующим путям. Каждый запрос приходит как объект event, из которого хелперы h3 достают тело, параметры, заголовки. Вернули значение — Nitro сериализует его в ответ. Магия в том, что один и тот же Nitro отвечает и за SSR страниц, и за API: это единый сервер.

Смоделируем мини-роутер Nitro: сопоставление пути с обработчиком и сериализацию ответа:

// Мини-Nitro: путь файла -> эндпоинт -> JSON.
function defineEventHandler(fn) { return fn; }   // как в Nuxt

// "Файлы" в server/api/
const handlers = {
  "/api/products": defineEventHandler(() => [
    { id: 1, name: "Книга" },
    { id: 2, name: "Ручка" },
  ]),
  "/api/hello": defineEventHandler((event) => ({
    message: "Привет, " + (event.name || "гость"),
  })),
};

function handleRequest(path, event) {
  const handler = handlers[path];
  if (!handler) return { status: 404, body: "Not Found" };
  const result = handler(event || {});
  return { status: 200, body: JSON.stringify(result) };  // -> JSON
}

console.log(handleRequest("/api/products"));
console.log(handleRequest("/api/hello", { name: "Алиса" }));
console.log(handleRequest("/api/unknown"));

Попробуй сам ▶ — путь сопоставляется с обработчиком, результат превращается в JSON, неизвестный путь даёт 404. Так же, но мощнее, работает Nitro.

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

  • Забыть defineEventHandler. Без обёртки Nitro не распознает обработчик.
  • Класть серверную логику вне server/. Только код в server/ гарантированно не попадёт в браузер.
  • Возвращать несериализуемое. Ответ идёт в JSON — функции и Map туда не положишь как есть.

Best practices

  • Всю работу с БД, секретами и внешними API делайте в server/api.
  • Возвращайте чистые JSON-объекты; статус и заголовки задавайте через хелперы h3.
  • Общую серверную логику выносите в server/utils — она тоже авто-импортируется.

Итог: server/api и Nitro дают полноценный бэкенд внутри Nuxt — это и делает его фулстек. Дальше научимся различать HTTP-методы и работать с динамическими серверными маршрутами.

Проверьте себя
1. Какому URL соответствует файл server/api/hello.ts?
A/hello
B/api/hello
C/server/api/hello
D/api/server/hello
2. Почему обращение к базе данных и секретам безопасно делать в server/api?
AПотому что там быстрее интернет
BКод в server/ исполняет Nitro на сервере и он никогда не попадает в браузерный бандл
CПотому что Nitro шифрует весь трафик автоматически
DЭто небезопасно, секреты лучше держать на клиенте