server

Директория server/ используется для регистрации API и серверных обработчиков приложения.

Nuxt автоматически сканирует файлы в этих директориях и регистрирует API и серверные обработчики с поддержкой Hot Module Replacement (HMR).

Структура каталогов
-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # логирование всех запросов

В каждом файле должен экспортироваться обработчик по умолчанию, определённый через defineEventHandler() или eventHandler() (алиас).

Обработчик может возвращать JSON, Promise или вызывать event.node.res.end() для отправки ответа.

server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: 'world',
  }
})

Этот API можно вызывать из страниц и компонентов:

app/pages/index.vue
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

Серверные маршруты

Файлы в ~~/server/api автоматически получают префикс /api в URL.

Чтобы добавить серверные маршруты без префикса /api, поместите их в директорию ~~/server/routes.

Пример:

server/routes/hello.ts
export default defineEventHandler(() => 'Привет, мир!')

В примере выше маршрут /hello будет доступен по адресу http://localhost:3000/hello.

Сейчас серверные маршруты не поддерживают полный функционал динамических маршрутов, как страницы.

Серверный мидлвар

Nuxt автоматически подхватывает файлы в ~~/server/middleware и создаёт серверный мидлвар для проекта.

Обработчики мидлвара выполняются при каждом запросе до любых других серверных маршрутов — для добавления или проверки заголовков, логирования запросов или расширения объекта запроса.

Обработчики мидлвара не должны ничего возвращать (и не должны закрывать или отвечать на запрос), а только проверять или расширять контекст запроса либо выбрасывать ошибку.

Примеры:

server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('Новый запрос: ' + getRequestURL(event))
})
server/middleware/auth.ts
export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

Серверные плагины

Nuxt автоматически подхватывает файлы в ~~/server/plugins и регистрирует их как плагины Nitro. Так можно расширять поведение Nitro и подключаться к событиям жизненного цикла.

Пример:

server/plugins/nitroPlugin.ts
export default defineNitroPlugin((nitroApp) => {
  console.log('Плагин Nitro', nitroApp)
})
Узнать больше Плагины Nitro.

Серверные утилиты

Серверные маршруты работают на базе h3js/h3, в котором есть набор вспомогательных функций.

Узнать больше Хелперы запросов H3.

Дополнительные хелперы можно добавлять в директории ~~/server/utils.

Например, можно определить обёртку над обработчиком, которая выполняет действия до и после основного обработчика.

Пример:

server/utils/handler.ts
export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D> (
  handler: EventHandler<T, D>,
): EventHandler<T, D> =>
  defineEventHandler<T>(async (event) => {
    try {
      // что-то до обработчика маршрута
      const response = await handler(event)
      // что-то после обработчика маршрута
      return { response }
    } catch (err) {
      // обработка ошибок
      return { err }
    }
  })
server/api/hello.get.ts
export default defineWrappedResponseHandler(event => 'hello world')

Алиас #server

Можно использовать алиас #server для импорта файлов из любой части директории server/, независимо от расположения импортирующего файла.

server/api/users/[id]/profile.ts
// Вместо относительных путей:
// import { formatUser } from '../../../utils/formatUser'

// Используйте алиас #server:
import { formatUser } from '#server/utils/formatUser'

Так импорты остаются единообразными, особенно в глубоко вложенных обработчиках маршрутов.

Алиас #server можно использовать только внутри директории server/. Импорт из #server в клиентском коде приведёт к ошибке.

Типы на сервере

Автоимпорты и типы в директории server/ отличаются от app/, так как код выполняется в другом контексте.

По умолчанию Nuxt 4 генерирует tsconfig.json с project reference для папки server/, что обеспечивает корректную типизацию.

Рецепты

Параметры маршрута

В именах файлов серверных маршрутов можно использовать динамические параметры в квадратных скобках, например /api/hello/[name].ts; доступ к ним — через event.context.params.

server/api/hello/[name].ts
export default defineEventHandler((event) => {
  const name = getRouterParam(event, 'name')

  return `Hello, ${name}!`
})
Для проверки и типов можно использовать getValidatedRouterParams с валидатором схемы, например Zod.

Вызов /api/hello/nuxt вернёт Hello, nuxt!.

Соответствие HTTP-методу

Имена файлов могут иметь суффикс .get, .post, .put, .delete и т.д., чтобы обрабатывать только указанный HTTP-метод.

server/api/test.get.ts
export default defineEventHandler(() => 'Test get handler')
server/api/test.post.ts
export default defineEventHandler(() => 'Test post handler')

В примере выше при обращении к /test:

  • GET — вернётся Test get handler
  • POST — вернётся Test post handler
  • любой другой метод — ответ 405

Для структурирования кода можно использовать index.[method].ts внутри директории (например, для API-неймспейсов).

export default defineEventHandler((event) => {
  // обработка GET для api/foo
})

Catch-all маршрут

Catch-all маршруты удобны как запасной обработчик.

Файл ~~/server/api/foo/[...].ts регистрирует catch-all для запросов, не совпадающих с другими обработчиками, например /api/foo/bar/baz.

server/api/foo/[...].ts
export default defineEventHandler((event) => {
  // event.context.path — путь маршрута: '/api/foo/bar/baz'
  // event.context.params._ — сегмент: 'bar/baz'
  return `Default foo handler`
})

Имя для catch-all задаётся так: ~~/server/api/foo/[...slug].ts; доступ — через event.context.params.slug.

server/api/foo/[...slug].ts
export default defineEventHandler((event) => {
  // event.context.params.slug — сегмент: 'bar/baz'
  return `Default foo handler`
})

Обработка тела запроса

server/api/submit.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})
Для проверки и типов можно использовать readValidatedBody с валидатором схемы, например Zod.

Вызов API:

app/app.vue
<script setup lang="ts">
async function submit () {
  const { body } = await $fetch('/api/submit', {
    method: 'post',
    body: { test: 123 },
  })
}
</script>
Суффикс submit.post.ts в имени файла нужен только для сопоставления с методом POST, который может принять тело запроса. Использование readBody в GET-обработчике приведёт к ошибке 405 Method Not Allowed.

Query-параметры

Пример запроса: /api/query?foo=bar&baz=qux

server/api/query.get.ts
export default defineEventHandler((event) => {
  const query = getQuery(event)

  return { a: query.foo, b: query.baz }
})
Для проверки и типов можно использовать getValidatedQuery с валидатором схемы, например Zod.

Обработка ошибок

Если ошибка не выбрасывается, возвращается статус 200 OK.

Любая необработанная ошибка вернёт 500 Internal Server Error.

Для других кодов ошибок используйте createError:

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  const id = Number.parseInt(event.context.params.id) as number

  if (!Number.isInteger(id)) {
    throw createError({
      status: 400,
      statusText: 'ID должен быть целым числом',
    })
  }
  return 'All good'
})

Коды статуса

Для возврата других кодов статуса используйте утилиту setResponseStatus.

Например, для 202 Accepted:

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  setResponseStatus(event, 202)
})

Runtime-конфигурация

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event)

  const repo = await $fetch('https://api.github.com/repos/nuxt/nuxt', {
    headers: {
      Authorization: `token ${config.githubToken}`,
    },
  })

  return repo
})
Передавать event в useRuntimeConfig необязательно, но рекомендуется — так в серверных маршрутах будет учитываться переопределение конфига переменными окружения.

Куки запроса

server/api/cookies.ts
export default defineEventHandler((event) => {
  const cookies = parseCookies(event)

  return { cookies }
})

Прокидывание контекста и заголовков

По умолчанию при запросах из серверных маршрутов не прокидываются ни заголовки входящего запроса, ни контекст. Используйте event.$fetch, чтобы прокинуть контекст и заголовки.

server/api/forward.ts
export default defineEventHandler((event) => {
  return event.$fetch('/api/forwarded')
})
Заголовки, которые не предназначены для прокидывания, не включаются в запрос. Например: transfer-encoding, connection, keep-alive, upgrade, expect, host, accept.

Ожидание промисов после ответа

Иногда нужно выполнить асинхронные задачи после отправки ответа клиенту (кэширование, логирование). Метод event.waitUntil позволяет ожидать промис в фоне, не задерживая ответ.

event.waitUntil принимает промис, который будет ожидаться до завершения обработчика, так что задача выполнится даже если сервер завершит обработчик сразу после отправки ответа. Это интегрируется с рантайм-провайдерами и их возможностями по отложенным задачам.

server/api/background-task.ts
const timeConsumingBackgroundTask = async () => {
  await new Promise(resolve => setTimeout(resolve, 1000))
}

export default eventHandler((event) => {
  // запланировать фоновую задачу без блокировки ответа
  event.waitUntil(timeConsumingBackgroundTask())

  // сразу отправить ответ клиенту
  return 'done'
})

Продвинутое использование

Конфигурация Nitro

В nuxt.config можно задать ключ nitro для прямой настройки Nitro.

Это продвинутая опция. Собственная конфигурация может повлиять на продакшен-деплой, так как интерфейс конфигурации может меняться при обновлении Nitro в рамках semver-minor версий Nuxt.
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.build/config
  nitro: {},
})
Узнать больше Docs > 4 X > Guide > Concepts > Server Engine.

Вложенный роутер

server/api/hello/[...slug].ts
import { createRouter, defineEventHandler, useBase } from 'h3'

const router = createRouter()

router.get('/test', defineEventHandler(() => 'Hello World'))

export default useBase('/api/hello', router.handler)

Отправка стримов

Экспериментальная возможность, доступна во всех окружениях.
server/api/foo.get.ts
import fs from 'node:fs'
import { sendStream } from 'h3'

export default defineEventHandler((event) => {
  return sendStream(event, fs.createReadStream('/path/to/file'))
})

Редирект

server/api/foo.get.ts
export default defineEventHandler(async (event) => {
  await sendRedirect(event, '/path/redirect/to', 302)
})

Устаревший обработчик или мидлвар

server/api/legacy.ts
export default fromNodeMiddleware((req, res) => {
  res.end('Legacy handler')
})
Поддержка устаревшего API возможна через h3js/h3, но по возможности лучше не использовать такие обработчики.
server/middleware/legacy.ts
export default fromNodeMiddleware((req, res, next) => {
  console.log('Legacy middleware')
  next()
})
Не сочетайте колбэк next() с устаревшим мидлваром, который является async или возвращает Promise.

Серверное хранилище

Nitro предоставляет кроссплатформенный слой хранилища. Дополнительные точки монтирования настраиваются через nitro.storage или серверные плагины.

Пример подключения Redis:

Через nitro.storage:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    storage: {
      redis: {
        driver: 'redis',
        /* redis connector options */
        port: 6379, // Redis port
        host: '127.0.0.1', // Redis host
        username: '', // needs Redis >= 6
        password: '',
        db: 0, // Defaults to 0
        tls: {}, // tls/ssl
      },
    },
  },
})

В обработчике API:

server/api/storage/test.ts
export default defineEventHandler(async (event) => {
  // List all keys with
  const keys = await useStorage('redis').getKeys()

  // Set a key with
  await useStorage('redis').setItem('foo', 'bar')

  // Remove a key with
  await useStorage('redis').removeItem('foo')

  return {}
})
Подробнее о слое хранилища Nitro.

Либо можно создать точку монтирования через серверный плагин и runtime config:

import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  const storage = useStorage()

  // Динамическая передача учётных данных из runtime config или других источников
  const driver = redisDriver({
    base: 'redis',
    host: useRuntimeConfig().redis.host,
    port: useRuntimeConfig().redis.port,
    /* other redis connector options */
  })

  // Монтирование драйвера
  storage.mount('redis', driver)
})