server

Каталог server/ регистрирует API и серверные обработчики приложения.

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

Directory structure
-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # log all requests

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

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

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

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

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

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

Роуты сервера

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

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

Пример:

server/routes/hello.ts
export default defineEventHandler(() => 'Hello World!')

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

Обратите внимание, что в настоящее время серверные маршруты не поддерживают полную функциональность динамических маршрутов, как это делают страницы.

Серверные middleware

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

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

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

Примеры:

server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('New request: ' + 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 plugin', nitroApp)
})
Узнать больше Nitro Plugins.

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

Роуты сервера работают на основе h3js/h3, который поставляется с удобным набором хелперов.

Узнать больше Available H3 Request Helpers.

Вы можете самостоятельно добавить больше хелперов в директорию ~~/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 {
      // do something before the route handler
      const response = await handler(event)
      // do something after the route handler
      return { response }
    } catch (err) {
      // Error handling
      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 в клиентском коде приведёт к ошибке.

Серверные типы

Эта функция доступна с Nuxt >= 3.5

Чтобы в IDE было проще различать автоматический импорт из Nitro и из Vue, добавьте ~/server/tsconfig.json со следующим содержимым:

server/tsconfig.json
{
  "extends": "../.nuxt/tsconfig.server.json"
}

В настоящее время эти значения не будут учитываться при проверке типов (nuxt typecheck), но вы должны получить более точные подсказки по типам в своей IDE.

Рецепты

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

Роуты сервера могут использовать динамические параметры в квадратных скобках в имени файла, например /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(() => 'Тестовый обработчик get')
server/api/test.post.ts
export default defineEventHandler(() => 'Тестовый обработчик post')

Учитывая пример выше, выборка /test с помощью:

  • Метода GET: Возвращает Тестовый обработчик get
  • Метода POST: Возвращает Тестовый обработчик post
  • Любого другого метода: Возвращает ошибку 405

Вы также можете использовать index.[method].ts внутри директории для структурирования кода по-другому. Это полезно для создания пространств имен API.

export default defineEventHandler((event) => {
  // обрабатывает GET-запросы для эндпоинта `api/foo`
})

Универсальные роуты

Универсальные роуты полезны для обработки всех остальных маршрутов.

Например, создание файла с именем ~/server/api/foo/[...].ts зарегистрирует универсальный роут для всех запросов, которые не соответствуют ни одному обработчику, например /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 `Обработчик foo по умолчанию`
})

Вы можете задать имя для универсального роута с помощью ~/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 `Обработчик foo по умолчанию`
})

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

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

Пример вызова API:

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, readBody выдаст ошибку HTTP 405 Method Not Allowed.

Параметры запроса

Пример запроса /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.

Любые неперехваченные ошибки вернут HTTP-ошибку 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({
      statusCode: 400,
      statusMessage: 'ID должен быть целым числом',
    })
  }
  return 'Все хорошо'
})

Коды статуса

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

Например, чтобы вернуть 202 Accepted

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

Конфигурация рантайма

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 }
})

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

По умолчанию ни заголовки входящего запроса, ни контекст запроса не передаются при выполнении fetch-запросов в серверных маршрутах. Вы можете использовать event.$fetch, чтобы передать контекст запроса и заголовки при выполнении 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

Вы можете использовать ключ nitro в nuxt.config, чтобы напрямую задать конфигурацию Nitro.

Это продвинутая опция. Собственная конфигурация может повлиять на развёртывание в продакшене: интерфейс настроек Nitro может меняться при обновлении Nitro в минорных версиях Nuxt.
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.build/config
  nitro: {},
})
Узнать больше Docs > 3 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)
})

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

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

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

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

Пример добавления хранилища Redis:

Использование nitro.storage:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    storage: {
      redis: {
        driver: 'redis',
        /* параметры коннектора redis */
        port: 6379, // порт Redis
        host: '127.0.0.1', // хост Redis
        username: '', // для Redis >= 6
        password: '',
        db: 0, // по умолчанию 0
        tls: {}, // tls/ssl
      },
    },
  },
})

Затем в вашем обработчике API:

server/api/storage/test.ts
export default defineEventHandler(async (event) => {
  // Список всех ключей
  const keys = await useStorage('redis').getKeys()

  // Установка ключа
  await useStorage('redis').setItem('foo', 'bar')

  // Удаление ключа
  await useStorage('redis').removeItem('foo')

  return {}
})
Узнайте больше о слое хранения Nitro.

В качестве альтернативы вы можете создать точку монтирования хранилища с помощью серверного плагина и конфигурации рантайма:

import redisDriver from 'unstorage/drivers/redis'

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

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

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