服务器
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() 发送响应。
export default defineEventHandler((event) => {
return {
hello: 'world',
}
})
你现在可以在页面和组件中通用地调用此 API
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>
<template>
<pre>{{ data }}</pre>
</template>
服务器路由
~~/server/api 内部的文件在其路由中会自动带有 /api 前缀。
若要添加没有 /api 前缀的服务端路由,请将它们放入 ~~/server/routes 目录中。
示例
export default defineEventHandler(() => 'Hello World!')
根据上述示例,可以通过 https://:3000/hello 访问 /hello 路由。
服务端中间件
Nuxt 会自动读取 ~~/server/middleware 中的任何文件,为你的项目创建服务端中间件。
中间件处理程序将在每个请求的最开始、在任何其他服务端路由之前运行,用于添加或检查头部信息、记录请求日志,或扩展事件的请求对象。
示例
export default defineEventHandler((event) => {
console.log('New request: ' + getRequestURL(event))
})
export default defineEventHandler((event) => {
event.context.auth = { user: 123 }
})
服务端插件
Nuxt 会自动读取 ~~/server/plugins 目录中的任何文件并将它们注册为 Nitro 插件。这允许扩展 Nitro 的运行时行为并挂载到生命周期事件中。
示例
export default defineNitroPlugin((nitroApp) => {
console.log('Nitro plugin', nitroApp)
})
服务端工具函数
服务端路由由 h3js/h3 提供支持,该库内置了一套便捷的辅助函数。
你可以在 ~~/server/utils 目录中自行添加更多辅助函数。
例如,你可以定义一个自定义的处理程序工具,它包装了原始处理程序,并在返回最终响应之前执行额外的操作。
示例
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 }
}
})
export default defineWrappedResponseHandler(event => 'hello world')
服务端别名 v4.3
你可以使用 #server 别名从 server/ 目录内的任何位置导入文件,而无需考虑导入文件的具体位置。
// Instead of relative paths like this:
// import { formatUser } from '../../../utils/formatUser'
// Use the #server alias:
import { formatUser } from '#server/utils/formatUser'
该别名确保了整个服务端代码中导入方式的一致性,在深度嵌套的路由处理程序中尤其有用。
#server 别名只能在 server/ 目录内使用。在客户端代码中从 #server 导入将导致错误。服务端类型
server/ 目录的自动导入和其他类型有所不同,因为它运行在与 app/ 目录不同的上下文中。
默认情况下,Nuxt 4 会生成一个包含覆盖 server/ 文件夹的项目引用的 tsconfig.json,以确保类型定义的准确性。
秘诀
路由参数
服务端路由可以在文件名中使用方括号包含动态参数,例如 /api/hello/[name].ts,并通过 event.context.params 进行访问。
export default defineEventHandler((event) => {
const name = getRouterParam(event, 'name')
return `Hello, ${name}!`
})
你现在可以在 /api/hello/nuxt 上通用地调用此 API 并获得 Hello, nuxt!。
匹配 HTTP 方法
处理程序文件名可以带有 .get、.post、.put、.delete 等后缀,以匹配请求的 HTTP 方法。
export default defineEventHandler(() => 'Test get handler')
export default defineEventHandler(() => 'Test post handler')
根据上述示例,通过以下方式请求 /test:
- GET 方法:返回
Test get handler - POST 方法:返回
Test post handler - 任何其他方法:返回 405 错误
你还可以在目录中使用 index.[method].ts 来以不同的方式组织代码,这对于创建 API 命名空间非常有用。
export default defineEventHandler((event) => {
// handle GET requests for the `api/foo` endpoint
})
export default defineEventHandler((event) => {
// handle POST requests for the `api/foo` endpoint
})
export default defineEventHandler((event) => {
// handle GET requests for the `api/foo/bar` endpoint
})
捕获所有路由
捕获所有路由(Catch-all routes)对于回退路由处理非常有用。
例如,创建一个名为 ~~/server/api/foo/[...].ts 的文件,将为所有未匹配任何路由处理程序的请求注册一个捕获所有路由,例如 /api/foo/bar/baz。
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 访问它。
export default defineEventHandler((event) => {
// event.context.params.slug to get the route segment: 'bar/baz'
return `Default foo handler`
})
请求体处理
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return { body }
})
你现在可以通过以下方式通用地调用此 API
<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
export default defineEventHandler((event) => {
const query = getQuery(event)
return { a: query.foo, b: query.baz }
})
错误处理
如果没有抛出错误,将返回状态码 200 OK。
任何未捕获的错误都将返回 500 Internal Server Error HTTP 错误。
若要返回其他错误代码,请使用 createError 抛出异常
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
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
})
export default defineNuxtConfig({
runtimeConfig: {
githubToken: '',
},
})
NUXT_GITHUB_TOKEN='<my-super-token>'
请求 Cookie
export default defineEventHandler((event) => {
const cookies = parseCookies(event)
return { cookies }
})
转发上下文与头部信息
默认情况下,在服务端路由中进行 fetch 请求时,既不会转发传入请求的头部信息,也不会转发请求上下文。你可以在服务端路由中使用 event.$fetch 来在发起 fetch 请求时转发请求上下文和头部信息。
export default defineEventHandler((event) => {
return event.$fetch('/api/forwarded')
})
transfer-encoding、connection、keep-alive、upgrade、expect、host、accept响应后等待 Promise
在处理服务端请求时,你可能需要执行不应阻塞客户端响应的异步任务(例如,缓存和日志记录)。你可以使用 event.waitUntil 在后台等待一个 promise,而不会延迟响应。
event.waitUntil 方法接受一个在处理程序终止前会被等待的 promise,从而确保即使服务器原本会在发送响应后立即终止处理程序,任务也能完成。这与运行时提供商集成,利用其原生能力在发送响应后处理异步操作。
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'
})
高级用法
Nitro 配置
你可以在 nuxt.config 中使用 nitro 键来直接设置 Nitro 配置。
export default defineNuxtConfig({
// https://nitro.net.cn/config
nitro: {},
})
嵌套路由器
import { createRouter, defineEventHandler, useBase } from 'h3'
const router = createRouter()
router.get('/test', defineEventHandler(() => 'Hello World'))
export default useBase('/api/hello', router.handler)
发送流
import fs from 'node:fs'
import { sendStream } from 'h3'
export default defineEventHandler((event) => {
return sendStream(event, fs.createReadStream('/path/to/file'))
})
发送重定向
export default defineEventHandler(async (event) => {
await sendRedirect(event, '/path/redirect/to', 302)
})
旧版处理程序或中间件
export default fromNodeMiddleware((req, res) => {
res.end('Legacy handler')
})
export default fromNodeMiddleware((req, res, next) => {
console.log('Legacy middleware')
next()
})
next() 回调与标记为 async 或返回 Promise 的旧版中间件结合使用。服务端存储
Nitro 提供了一个跨平台的 存储层。为了配置额外的存储挂载点,你可以使用 nitro.storage 或 服务端插件。
添加 Redis 存储的示例
使用 nitro.storage
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
},
},
},
})
然后在你的 API 处理程序中
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 {}
})
或者,你也可以使用服务端插件和运行时配置来创建存储挂载点
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)
})
export default defineNuxtConfig({
runtimeConfig: {
redis: { // Default values
host: '',
port: 0,
/* other redis connector options */
},
},
})