Documentación offline Nuxt main

server

main Documentación oficial Licencia MITDescargado el 2026-08-02

En esta página

Nuxt automatically scans files inside these directories to register API and server handlers with Hot Module Replacement (HMR) support.

```bash [Directory Structure] -| server/ ---| api/ -----| hello.ts # /api/hello ---| routes/ -----| bonjour.ts # /bonjour ---| middleware/ -----| log.ts # log all requests

Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias).

The handler can directly return JSON data, a `Promise`, or a `Response` object.

::important
Do not import Vue app code (components, composables, or other app-only utilities) in your server routes or utilities, and do not import server-only code in your app.
::

:read-more{to="/docs/4.x/directory-structure/shared#why-you-cannot-mix-vue-and-nitro-code" title="Why You Cannot Mix Vue and Nitro Code"}

```ts twoslash [server/api/hello.ts]
import { defineEventHandler } from 'nitro/h3'

export default defineEventHandler((event) => {
  return {
    hello: 'world',
  }
})

You can now universally call this API in your pages and components:

```vue [app/pages/index.vue]

## Server Routes

Files inside the `~~/server/api` are automatically prefixed with `/api` in their route.

:video-accordion{title="Watch a video from Vue School on API routes" videoId="761468863" platform="vimeo"}

To add server routes without `/api` prefix, put them into `~~/server/routes` directory.

**Example:**

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

Given the example above, the /hello route will be accessible at http://localhost:3000/hello.

::note Note that currently server routes do not support the full functionality of dynamic routes as pages do. ::

Server Middleware#

Nuxt will automatically read in any file in the ~~/server/middleware to create server middleware for your project.

Middleware handlers will run on every request before any other server route to add or check headers, log requests, or extend the event's request object.

::note Middleware handlers should not return anything (nor close or respond to the request) and only inspect or extend the request context or throw an error. ::

Examples:

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

Server Plugins#

Nuxt will automatically read any files in the ~~/server/plugins directory and register them as Nitro plugins. This allows extending Nitro's runtime behavior and hooking into lifecycle events.

Example:

```ts [server/plugins/nitroPlugin.ts] import { definePlugin } from 'nitro'

export default definePlugin((nitroApp) => { console.log('Nitro plugin', nitroApp) })

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

## Server Utilities

Server routes are powered by [h3js/h3](https://github.com/h3js/h3) which comes with a handy set of helpers.

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

You can add more helpers yourself inside the `~~/server/utils` directory.

For example, you can define a custom handler utility that wraps the original handler and performs additional operations before returning the final response.

**Example:**

```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 Alias :badge[v4.3]{color="info" size="xs" class="align-middle"}

You can use the `#server` alias to import files from anywhere within the `server/` directory, regardless of the importing file's location.

```ts [server/api/users/[id]/profile.ts]
// Instead of relative paths like this:
// import { formatUser } from '../../../utils/formatUser'

// Use the #server alias:
import { formatUser } from '#server/utils/formatUser'

This alias ensures consistent imports across your server code, especially useful in deeply nested route handlers.

::note The #server alias can only be used within the server/ directory. Importing from #server in client code will result in an error. ::

Server Types#

Auto-imports and other types are different for the server/ directory, as it is running in a different context from the app/ directory.

By default, Nuxt 4 generates a tsconfig.json which includes a project reference covering the server/ folder which ensures accurate typings.

Types placed in ~~/server/types/ are auto-imported in the server context only, so you can reference them in server routes, middleware, plugins, and utilities without importing them. Types that are also needed in the Vue app belong in shared/types/ instead.

```ts twoslash [server/types/todo.ts] export interface Todo { id: string title: string completed: boolean }

```ts [server/api/todos.get.ts]
import { defineEventHandler } from 'nitro/h3'

export default defineEventHandler((): Todo[] => {
  return []
})

Only files directly in server/types/ are scanned; files in nested subdirectories are not auto-imported, matching how shared/types/ works.

Recipes#

Route Parameters#

Server routes can use dynamic parameters within brackets in the file name like /api/hello/[name].ts and be accessed via 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"}
Alternatively, use `getValidatedRouterParams` with a schema validator such as Zod for runtime and type safety.
::

You can now universally call this API on `/api/hello/nuxt` and get `Hello, nuxt!`.

### Matching HTTP Method

Handle file names can be suffixed with `.get`, `.post`, `.put`, `.delete`, ... to match request's [HTTP Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods).

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

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

Given the example above, fetching `/test` with:

- **GET** method: Returns `Test get handler`
- **POST** method: Returns `Test post handler`
- Any other method: Returns 405 error

You can also use `index.[method].ts` inside a directory for structuring your code differently, this is useful to create API namespaces.

::code-group
```ts [server/api/foo/index.get.ts]
export default defineEventHandler((event) => {
  // handle GET requests for the `api/foo` endpoint
})

``ts [server/api/foo/index.post.ts] export default defineEventHandler((event) => { // handle POST requests for theapi/foo` endpoint })

```ts [server/api/foo/bar.get.ts]
export default defineEventHandler((event) => {
  // handle GET requests for the `api/foo/bar` endpoint
})

::

Catch-all Route#

Catch-all routes are helpful for fallback route handling.

For example, creating a file named ~~/server/api/foo/[...].ts will register a catch-all route for all requests that do not match any route handler, such as /api/foo/bar/baz.

``ts [server/api/foo/[...\\].ts] export default defineEventHandler((event) => { // event.context.path to get the route path: '/api/foo/bar/baz' // event.context.params._ to get the route segment: 'bar/baz' returnDefault foo handler` })

You can set a name for the catch-all route by using `~~/server/api/foo/[...slug].ts` and access it via `event.context.params.slug`.

```ts [server/api/foo/[...slug\\].ts]
export default defineEventHandler((event) => {
  // event.context.params.slug to get the route segment: 'bar/baz'
  return `Default foo handler`
})

Body Handling#

```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"}
Alternatively, use `readValidatedBody` with a schema validator such as Zod for runtime and type safety.
::

You can now universally call this API using:

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

::note We are using submit.post.ts in the filename only to match requests with POST method that can accept the request body. When using readBody within a GET request, readBody will throw a 405 Method Not Allowed HTTP error. ::

Query Parameters#

Sample query /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"}
Alternatively, use `getValidatedQuery` with a schema validator such as Zod for runtime and type safety.
::

### Error Handling

If no errors are thrown, a status code of `200 OK` will be returned.

Any uncaught errors will return a `500 Internal Server Error` HTTP Error.

To return other error codes, throw an exception with [`createError`](/docs/4.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({
      status: 400,
      statusText: 'ID should be an integer',
    })
  }
  return 'All good'
})

Status Codes#

To return other status codes, use the setResponseStatus utility.

For example, to return 202 Accepted

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

### Runtime Config

::code-group
```ts [server/api/foo.ts]
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()

  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>'

::

Request Cookies#

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

return { cookies } })

### Forwarding Context & Headers

By default, neither the headers from the incoming request nor the request context are forwarded when
making fetch requests in server routes. You can use `event.$fetch` to forward the request context and headers when making fetch requests in server routes.

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

::note Headers that are not meant to be forwarded will not be included in the request. These headers include, for example: transfer-encoding, connection, keep-alive, upgrade, expect, host, accept ::

Awaiting Promises After Response#

When handling server requests, you might need to perform asynchronous tasks that shouldn't block the response to the client (for example, caching and logging). You can use event.waitUntil to await a promise in the background without delaying the response.

The event.waitUntil method accepts a promise that will be awaited before the handler terminates, ensuring the task is completed even if the server would otherwise terminate the handler right after the response is sent. This integrates with runtime providers to leverage their native capabilities for handling asynchronous operations after the response is sent.

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

export default eventHandler((event) => { // schedule a background task without blocking the response event.waitUntil(timeConsumingBackgroundTask())

// immediately send the response to the client return 'done' })

## Advanced Usage

### Nitro Config

You can use `nitro` key in `nuxt.config` to directly set [Nitro configuration](https://nitro.build/config).

::warning
This is an advanced option. Custom config can affect production deployments, as the configuration interface might change over time when Nitro is upgraded in semver-minor versions of Nuxt.
::

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

:read-more{to="/docs/4.x/guide/concepts/server-engine"}

Nested Router#

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

### Sending Streams

::tip
This is an experimental feature and is available in all environments.
::

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

Sending Redirect#

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

### Legacy Handler or Middleware

```ts [server/api/legacy.ts]
export default fromNodeMiddleware((req, res) => {
  res.end('Legacy handler')
})

::important Legacy support is possible using h3js/h3, but it is advised to avoid legacy handlers as much as you can. ::

```ts [server/middleware/legacy.ts] export default fromNodeMiddleware((req, res, next) => { console.log('Legacy middleware') next() })

::warning
Never combine `next()` callback with a legacy middleware that is `async` or returns a `Promise`.
::

### Server Storage

Nitro provides a cross-platform [storage layer](https://nitro.build/guide/storage). In order to configure additional storage mount points, you can use `nitro.storage`, or [server plugins](/docs/4.x/directory-structure/server#server-plugins).

**Example of adding a Redis storage:**

Using `nitro.storage`:

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

Then in your API handler:

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

::read-more{to="https://nitro.build/guide/storage" target="_blank"}
Read more about Nitro Storage Layer.
::

Alternatively, you can create a storage mount point using a server plugin and runtime config:

::code-group
```ts [server/plugins/storage.ts]
import { definePlugin } from 'nitro'
import redisDriver from 'unstorage/drivers/redis'

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

  // Dynamically pass in credentials from runtime configuration, or other sources
  const driver = redisDriver({
    base: 'redis',
    host: useRuntimeConfig().redis.host,
    port: useRuntimeConfig().redis.port,
    /* other redis connector options */
  })

  // Mount driver
  storage.mount('redis', driver)
})

ts [nuxt.config.ts] export default defineNuxtConfig({ runtimeConfig: { redis: { // Default values host: '', port: 0, /* other redis connector options */ }, }, }) ::