server

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

Nuxt 会自动扫描这些目录下的文件,以注册 API 和服务器处理程序,并支持热模块替换(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:

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 访问。

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

服务器中间件

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)
})
Read more in Nitro 插件.

服务器工具

服务器路由由 unjs/h3 驱动,附带一套方便的辅助工具。

Read more in 可用的 H3 请求辅助工具.

你还可以在 ~/server/utils 目录添加更多辅助工具。

例如,可以定义一个自定义的处理器工具,包装原始处理器并在返回最终响应之前执行额外操作。

示例:

server/utils/handler.ts
import type { EventHandler, EventHandlerRequest } from 'h3'

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

服务器类型

此功能从 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}!`
})
另外,可以结合 Zod 等模式验证器,使用 getValidatedRouterParams 实现运行时和类型安全。

现在你可以在 /api/hello/nuxt 调用此 API,得到 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 处理器')

以上示例:

  • 使用 GET 方法请求 /test 返回 测试 GET 处理器
  • 使用 POST 方法请求 /test 返回 测试 POST 处理器
  • 其他方法返回 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 `默认的 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 }
})
另外,可结合 Zod 等模式验证器使用 readValidatedBody 实现运行时及类型安全。

现在可以这样调用 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 方法。当在 GET 请求中使用 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 }
})
另外,结合 Zod 等模式验证器使用 getValidatedQuery 可实现运行时和类型安全。

错误处理

如果没有抛出错误,默认返回状态码 200 OK

任何未捕获的错误返回 HTTP 500 内部服务器错误。

要返回其他错误码,可抛出由 createError 创建的异常:

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  const id = 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
})
useRuntimeConfig 传入 event 参数是可选的,但推荐传入,以便服务器路由运行时能从 环境变量 覆盖配置。

请求 Cookies

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

  return { cookies }
})

转发上下文和头部

默认情况下,在服务器路由中发起 fetch 请求时,不会转发来自请求的头部和请求上下文。你可以使用 event.$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 '完成'
});

高级用法

Nitro 配置

你可以在 nuxt.config 中使用 nitro 配置项,直接设置 Nitro 配置

这是高级选项。自定义配置可能影响生产部署,因为 Nitro 在 Nuxt 子版本升级时可能会更改配置接口。
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.zhcndoc.com/config
  nitro: {}
})
Read more in Docs > 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('传统处理器')
})
可借助 unjs/h3 实现传统支持,但建议尽量避免使用传统处理器。
server/middleware/legacy.ts
export default fromNodeMiddleware((req, res, next) => {
  console.log('传统中间件')
  next()
})
切勿将 next() 回调与返回 Promise 或为 async 的传统中间件混用。

服务器存储

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