# server

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

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

```bash [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()` для отправки ответа.

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

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

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

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

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

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

<video-accordion platform="vimeo" title="Видео Vue School о маршрутах API" video-id="761468863">



</video-accordion>

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

**Пример:**

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

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

<note>

Обратите внимание, что в настоящее время серверные маршруты не поддерживают полную функциональность динамических маршрутов, как это делают [страницы](/docs/3.x/directory-structure/pages#dynamic-routes).

</note>

## Серверные middleware

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

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

<note>

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

</note>

**Примеры:**

```ts [server/middleware/log.ts]
export default defineEventHandler((event) => {
  console.log('New request: ' + getRequestURL(event))
})
```

```ts [server/middleware/auth.ts]
export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})
```

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

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

**Пример:**

```ts [server/plugins/nitroPlugin.ts]
export default defineNitroPlugin((nitroApp) => {
  console.log('Nitro plugin', nitroApp)
})
```

<read-more to="https://nitro.build/guide/plugins" target="_blank" title="Nitro Plugins">



</read-more>

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

Роуты сервера работают на основе [h3js/h3](https://github.com/h3js/h3), который поставляется с удобным набором хелперов.

<read-more to="https://www.jsdocs.io/package/h3#package-index-functions" target="_blank" title="Available H3 Request Helpers">



</read-more>

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

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

**Пример:**

```ts [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 }
    }
  })
```

```ts [server/api/hello.get.ts]
export default defineWrappedResponseHandler(event => 'hello world')
```

## Серверный алиас `#server`

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

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

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

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

<note>

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

</note>

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

<tip>

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

</tip>

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

```json [server/tsconfig.json]
{
  "extends": "../.nuxt/tsconfig.server.json"
}
```

В настоящее время эти значения не будут учитываться при проверке типов ([`nuxt typecheck`](/docs/3.x/api/commands/typecheck)), но вы должны получить более точные подсказки по типам в своей IDE.

## Рецепты

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

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

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

  return `Hello, ${name}!`
})
```

<tip to="https://h3.dev/examples/validate-data#validate-params">

В качестве альтернативы используйте `getValidatedRouterParams` с валидатором схемы, таким как Zod, для обеспечения безопасности рантайма и безопасности типов.

</tip>

Запрос к `/api/hello/nuxt` вернёт ответ `Hello, nuxt!`.

### Соответствие метода HTTP

Имена файлов обработчиков могут иметь суффиксы `.get`, `.post`, `.put`, `.delete`, … в соответствии с [методом HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) запроса.

```ts [server/api/test.get.ts]
export default defineEventHandler(() => 'Тестовый обработчик get')
```

```ts [server/api/test.post.ts]
export default defineEventHandler(() => 'Тестовый обработчик post')
```

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

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

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

<code-group>

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

```ts [server/api/foo/index.post.ts]
export default defineEventHandler((event) => {
  // обрабатывает POST-запросы для эндпоинта `api/foo`
})
```

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

</code-group>

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

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

Например, создание файла с именем `~/server/api/foo/[...].ts` зарегистрирует универсальный роут для всех запросов, которые не соответствуют ни одному обработчику, например `/api/foo/bar/baz`.

```ts [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`.

```ts [server/api/foo/[...slug].ts]
export default defineEventHandler((event) => {
  // event.context.params.slug чтобы получить сегмент роута: 'bar/baz'
  return `Обработчик foo по умолчанию`
})
```

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

```ts [server/api/submit.post.ts]
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})
```

<tip to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web#runtime-type-safe-request-utils">

В качестве альтернативы используйте `readValidatedBody` с валидатором схемы, таким как Zod, для обеспечения безопасности рантайма и безопасности типов.

</tip>

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

```vue [app.vue]
<script setup lang="ts">
async function submit () {
  const { body } = await $fetch('/api/submit', {
    method: 'post',
    body: { test: 123 },
  })
}
</script>
```

<note>

Мы используем `submit.post.ts` в имени файла только для сопоставления запросов с методом `POST`, который может принять тело запроса. При использовании `readBody` в запросе GET, `readBody` выдаст ошибку HTTP `405 Method Not Allowed`.

</note>

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

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

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

  return { a: query.foo, b: query.baz }
})
```

<tip to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web#runtime-type-safe-request-utils">

В качестве альтернативы используйте `getValidatedQuery` с валидатором схемы, таким как Zod, для обеспечения безопасности рантайма и безопасности типов.

</tip>

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

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

Любые неперехваченные ошибки вернут HTTP-ошибку `500 Internal Server Error`.

Чтобы вернуть другие коды ошибок, вызовите исключение с помощью [`createError`](/docs/3.x/api/utils/create-error):

```ts [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`](/docs/3.x/api/utils/set-response-status).

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

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

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

<code-group>

```ts [server/api/foo.ts]
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
})
```

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  runtimeConfig: {
    githubToken: '',
  },
})
```

```ini [.env]
NUXT_GITHUB_TOKEN='<my-super-token>'
```

</code-group>

<note>

Указание `event` в качестве аргумента `useRuntimeConfig` необязательно, но рекомендуется передать его, чтобы перезаписать конфигурацию рантайма [переменными окружения](/docs/3.x/guide/going-further/runtime-config#environment-variables) во время выполнения для серверных роутов.

</note>

### Работа с cookie

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

  return { cookies }
})
```

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

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

```ts [server/api/forward.ts]
export default defineEventHandler((event) => {
  return event.$fetch('/api/forwarded')
})
```

<note>

Заголовки, которые **не предназначены** для передачи, **не будут включены** в запрос. К таким заголовкам относятся, например:
`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept`

</note>

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

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

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

```ts [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](https://nitro.build/config).

<warning>

Это продвинутая опция. Собственная конфигурация может повлиять на развёртывание в продакшене: интерфейс настроек Nitro может меняться при обновлении Nitro в минорных версиях Nuxt.

</warning>

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  // https://nitro.build/config
  nitro: {},
})
```

<read-more to="/docs/3.x/guide/concepts/server-engine">



</read-more>

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

```ts [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)
```

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

<tip>

Это экспериментальная функция, доступная во всех окружениях.

</tip>

```ts [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'))
})
```

### Отправка редиректа

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

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

```ts [server/api/legacy.ts]
export default fromNodeMiddleware((req, res) => {
  res.end('Устаревший обработчик')
})
```

<important>

Поддержка устаревших обработчиков есть в [h3js/h3](https://github.com/h3js/h3), но по возможности лучше их не использовать.

</important>

```ts [server/middleware/legacy.ts]
export default fromNodeMiddleware((req, res, next) => {
  console.log('Устаревшая middleware')
  next()
})
```

<warning>

Не сочетайте вызов `next()` с legacy-middleware, объявленной как `async` или возвращающей `Promise`.

</warning>

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

Nitro предоставляет кроссплатформенный [слой хранения](https://nitro.build/guide/storage). Для настройки дополнительных точек монтирования хранилища можно использовать `nitro.storage` или [серверные плагины](#%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D1%8B).

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

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

```ts [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:

```ts [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 {}
})
```

<read-more to="https://nitro.build/guide/storage" target="_blank">

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

</read-more>

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

<code-group>

```ts [server/plugins/storage.ts]
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)
})
```

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  runtimeConfig: {
    redis: { // Значения по умолчанию
      host: '',
      port: 0,
      /* другие опции коннектора redis */
    },
  },
})
```

</code-group>
