server

server/ 目录用于向应用程序注册 API 和服务器处理程序。

Nuxt 会自动扫描这些目录中的文件,以注册支持热模块替换(HMR)的 API 和服务器处理程序。

目录结构
|-| 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 前缀。

要添加不带 /api 前缀的服务器路由,请将它们放入 ~~/server/routes 目录。

示例:

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

基于上面的示例,/hello 路由将在 http://localhost:3000/hello 上可访问。

请注意,目前服务器路由不支持页面那样的动态路由完整功能。

服务器中间件

Nuxt 将自动读取 ~~/server/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 插件 中阅读更多。

服务器工具

服务器路由由 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/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,其中包含覆盖 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 上通用调用此 API 并获得 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) => {
  // 处理 `api/foo` 端点的 GET 请求
})

捕获所有路由

捕获所有路由有助于回退路由处理。

例如,创建一个名为 ~~/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 `Default foo handler`
})

您可以通过使用 ~~/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 方法的请求。在 GET 请求中使用 readBody 时,readBody 将抛出 405 Method Not Allowed HTTP 错误。

查询参数

示例查询 /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 HTTP 错误。

要返回其他错误代码,请使用 `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 should be an integer',
    })
  }
  return 'All good'
})

状态码

要返回其他状态码,请使用 `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-encodingconnectionkeep-aliveupgradeexpecthostaccept

响应后等待 Promise

在处理服务器请求时,您可能需要执行不应阻塞向客户端响应的异步任务(例如,缓存和日志记录)。您可以使用 event.waitUntil 在后台等待 promise,而不会延迟响应。

event.waitUntil 方法接受一个将在处理程序终止前等待的 promise,确保任务完成,即使服务器在发送响应后立即终止处理程序。这与运行时提供程序集成,以利用它们在发送响应后处理异步操作的原生功能。

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 配置

这是一个高级选项。自定义配置可能会影响生产部署,因为在 Nuxt 的 semver 次版本升级 Nitro 时,配置接口可能会随时间变化。
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.build/config
  nitro: {},
})
https://nitro.build 中阅读更多。

嵌套路由器

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')
})
可以使用 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 连接器选项 */
        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 {}
})
https://nitro.build/guide/storage 中阅读更多。

阅读有关 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)
})