事件处理
所有服务端代码都围绕 H3 的 event 对象进行请求和响应处理。event 是服务端开发的核心对象,理解它就能掌握服务端数据流转。
H3Event 对象
export default defineEventHandler((event) => {
// event 是 H3Event 实例
// 包含请求和响应的所有信息
})属性一览
| 属性 | 类型 | 说明 |
|---|---|---|
event.path | string | 请求路径(如 /api/users?page=1 中的 /api/users) |
event.method | string | HTTP 方法(GET、POST、PUT、DELETE 等) |
event.headers | Headers | 请求头对象 |
event.context | object | 上下文数据(中间件设置,路由读取) |
event.node.req | IncomingMessage | Node.js 原始请求对象 |
event.node.res | ServerResponse | Node.js 原始响应对象 |
event.context 是最重要的属性
它是在中间件和路由之间传递数据的桥梁。中间件设置 event.context.user、event.context.db 等,路由直接读取使用。
请求处理
readBody —— 读取请求体
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// POST /api/users body: { name: 'Alice', email: 'alice@example.com' }
// body = { name: 'Alice', email: 'alice@example.com' }
return { received: body }
})readBody 的智能解析
Content-Type: application/json→ 自动 JSON 解析Content-Type: application/x-www-form-urlencoded→ 自动 URL 编码解析Content-Type: multipart/form-data→ 自动解析 FormData- 其他 → 返回字符串
INFO
️ readBody 只能调用一次:请求体是流式数据 读取一次后就被消费了。如果需要多次使用,将结果存到变量中
readValidatedBody —— 校验请求体
使用 Zod 等 Schema 校验库验证请求体:
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2, '姓名至少2个字符'),
email: z.string().email('邮箱格式不正确'),
age: z.number().min(0).max(150).optional(),
})
export default defineEventHandler(async (event) => {
const body = await readValidatedBody(event, schema.parse)
// 校验失败时,自动抛出 400 错误
// 校验成功时,body 的类型由 schema 推断
return { user: body }
})readValidatedBody 的优势
- 自动校验 + 自动错误响应(400)
- TypeScript 类型推断(body 的类型由 schema 决定)
- 统一的错误格式
推荐所有 POST/PUT 请求都使用 readValidatedBody
避免手动校验。
getQuery —— 获取查询参数
// GET /api/search?q=nuxt&page=1
export default defineEventHandler((event) => {
const query = getQuery(event)
// query = { q: 'nuxt', page: '1' } 注意:值都是 string!
return { query }
})INFO
️ 查询参数的值都是 string 类型:即使 URL 中是 ?page=1query.page 也是字符串 '1'。需要手动转换或使用 getValidatedQuery
getValidatedQuery —— 校验查询参数
const schema = z.object({
page: z.coerce.number().min(1).default(1),
size: z.coerce.number().min(1).max(100).default(20),
sort: z.enum(['name', 'date', 'price']).default('date'),
})
export default defineEventHandler(async (event) => {
const { page, size, sort } = await getValidatedQuery(event, schema.parse)
// page 和 size 已经是 number 类型!
return { page, size, sort }
})z.coerce.number()
Zod 的 coerce 会自动将字符串转为数字,非常适合查询参数校验。
getRouterParam —— 获取路由参数
// server/api/users/[id].ts → /api/users/123
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
// id = '123'(string 类型)
if (!id || !/^\d+$/.test(id)) {
throw createError({ statusCode: 400, statusMessage: 'Bad Request', message: 'Invalid ID' })
}
return { id: Number(id) }
})路由参数也是 string 类型
需要手动校验和转换。推荐封装为工具函数:
// server/utils/params.ts
export const getNumericId = (event: H3Event, name = 'id') => {
const param = getRouterParam(event, name)
if (!param || !/^\d+$/.test(param)) {
throw createError({ statusCode: 400, statusMessage: 'Bad Request', message: `Invalid ${name}` })
}
return Number(param)
}getRequestHeader —— 获取请求头
export default defineEventHandler((event) => {
const auth = getRequestHeader(event, 'authorization')
const contentType = getRequestHeader(event, 'content-type')
const userAgent = getRequestHeader(event, 'user-agent')
return { auth, contentType, userAgent }
})响应处理
setResponseStatus —— 设置状态码
export default defineEventHandler((event) => {
setResponseStatus(event, 201) // Created
return { created: true }
})常用状态码
| 状态码 | 含义 | 适用场景 |
|---|---|---|
200 | OK | 成功(默认) |
201 | Created | 创建资源成功 |
204 | No Content | 删除成功(无返回体) |
301 | Moved Permanently | 永久重定向 |
302 | Found | 临时重定向 |
400 | Bad Request | 参数错误 |
401 | Unauthorized | 未认证 |
403 | Forbidden | 无权限 |
404 | Not Found | 资源不存在 |
429 | Too Many Requests | 请求过于频繁 |
setResponseHeader —— 设置响应头
export default defineEventHandler((event) => {
setResponseHeader(event, 'cache-control', 'max-age=3600')
setResponseHeader(event, 'x-custom', 'value')
return { message: 'Hello' }
})setCookie / getCookie / deleteCookie —— Cookie 操作
export default defineEventHandler((event) => {
// 读取 Cookie
const token = getCookie(event, 'auth-token')
// 设置 Cookie
setCookie(event, 'auth-token', 'new-token-value', {
httpOnly: true, // 不允许 JS 访问(防 XSS)
secure: true, // 仅 HTTPS 传输
sameSite: 'lax', // 防 CSRF
maxAge: 60 * 60 * 24 * 7, // 7 天(秒)
path: '/', // Cookie 路径
})
// 删除 Cookie
deleteCookie(event, 'temp-token')
return { token }
})Cookie 安全选项
httpOnly: true:JavaScript 无法读取(防止 XSS 窃取)secure: true:仅在 HTTPS 下传输sameSite: 'lax':防止 CSRF 攻击
认证 Token 推荐存储在 Cookie 中
(而非 localStorage),配合 httpOnly 更安全。
sendRedirect —— 重定向
export default defineEventHandler(async (event) => {
// 302 临时重定向
await sendRedirect(event, '/new-page', 302)
// 301 永久重定向
// await sendRedirect(event, '/new-page', 301)
})sendStream —— 发送流
import { createReadStream } from 'fs'
export default defineEventHandler((event) => {
setResponseHeader(event, 'content-type', 'application/pdf')
return sendStream(event, createReadStream('/path/to/file.pdf'))
})sendStream 的适用场景
- 大文件下载(不占用内存)
- 视频流
- SSE(Server-Sent Events)
event.$fetch —— 转发请求
在服务端使用 event.$fetch 可以转发请求上下文和头信息:
export default defineEventHandler((event) => {
// 转发请求头(如 Authorization、Cookies)
return event.$fetch('/api/internal/data')
})event.$fetch vs 普通 $fetch
event.$fetch:继承当前请求的上下文(headers、cookies),适合服务端内部转发- 普通
$fetch:不继承上下文,适合调用外部 API
event.waitUntil —— 后台任务
在响应发送后执行后台任务,不阻塞响应:
export default defineEventHandler((event) => {
// 响应后执行日志记录(不阻塞当前响应)
event.waitUntil(
logActivity(event.context.user.id, 'page_view')
)
// 响应后发送通知
event.waitUntil(
sendNotification(user.email, 'Your order has been processed')
)
return { status: 'ok' } // 立即返回,不等后台任务
})event.waitUntil 的适用场景
- 日志记录
- 数据统计
- 发送通知
- 缓存预热
event.waitUntil 确保后台任务在服务器关闭前完成
即使响应已经发送。
错误处理
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
// 抛出错误——自动返回错误响应
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
message: 'ID is required',
})
}
if (!/^\d+$/.test(id)) {
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
message: 'ID must be a number',
})
}
// 自定义错误数据
const user = getUserById(Number(id))
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
message: 'User not found',
data: { userId: id }, // 附加数据
})
}
return user
})createError 的行为
- 抛出 H3Error 对象
- H3 捕获后自动转为 HTTP 错误响应
- 响应体格式:
{ statusCode, statusMessage, data, message } - 后续中间件和路由不会执行
INFO
️ statusMessage 应使用简短的 HTTP 状态描述(如 "Bad Request"、"Not Found") 不要放中文或长文本。详细的错误描述应放在 message 中。详见 错误创建与抛出
知识脉络
API 路由 → 服务路由 → 服务中间件 → 服务插件 → 工具函数 → 你在这里:事件处理
│
├─→ 下一步:预渲染
│
└─→ 相关:错误处理(createError 详解)