server

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

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

目录结构
-| 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 访问。

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

服务器中间件

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

服务器类型

此功能适用于 Nuxt >= 3.5

为了在您的 IDE 中提高 'nitro' 和 'vue' 之间的清晰度,您可以在 ~/server/tsconfig.json 中添加以下内容:

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

目前,这些值不会在类型检查 (nuxi 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 上普遍调用此 API 并获取 你好, 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')

上面示例中,以:

  • GET 方法:返回 测试 get 处理程序
  • POST 方法:返回 测试 post 处理程序
  • 其他任何方法:返回 405 错误

您也可以在目录中使用 index.[method].ts 来以不同的方式结构您的代码,这对于创建 API 命名空间很有用。

export default defineEventHandler((event) => {
  // handle GET requests for the `api/foo` endpoint
})

通配符路由

通配符路由对于回退路由处理很有用。

例如,创建一个名为 ~/server/api/foo/[...].ts 的文件将注册一个通配符路由,用于处理不匹配任何路由处理程序的所有请求,例如 /api/foo/bar/baz

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'
  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 to get the route segment: '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 来普遍调用此 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 时,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 = parseInt(event.context.params.id) as number

  if (!Number.isInteger(id)) {
    throw createError({
      statusCode: 400,
      statusMessage: '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 是可选的,但建议在运行时将其传递以使服务器路由中的运行时配置被环境变量覆盖。

请求 Cookies

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

高级使用

Nitro 配置

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

这是一个高级选项。自定义配置可能会影响生产部署,因为配置界面可能会在 Nuxt 升级到 semver-minor 版本时发生变化。
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.unjs.io/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('Legacy handler')
})
不建议使用遗留中间件,请尽可能避免使用遗留中间件。
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) => {
  // 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 {}
})
阅读更多关于 Nitro 存储层。

或者,您可以使用服务器插件和运行时配置创建存储挂载点:

import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  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)
})