nuxt-server
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNuxt 4 Server Development
Nuxt 4 服务端开发
Server routes, API patterns, and backend development with Nitro.
基于Nitro实现服务端路由、API模式与后端开发。
Quick Reference
快速参考
File-Based Server Routes
基于文件的服务端路由
server/
├── api/ # API endpoints (/api/*)
│ ├── users/
│ │ ├── index.get.ts → GET /api/users
│ │ ├── index.post.ts → POST /api/users
│ │ ├── [id].get.ts → GET /api/users/:id
│ │ ├── [id].put.ts → PUT /api/users/:id
│ │ └── [id].delete.ts → DELETE /api/users/:id
│ └── health.get.ts → GET /api/health
├── routes/ # Non-API routes
│ └── sitemap.xml.get.ts → GET /sitemap.xml
├── middleware/ # Server middleware
│ └── auth.ts # Runs on every request
├── plugins/ # Nitro plugins
│ └── database.ts # Initialize database
└── utils/ # Server utilities
└── db.ts # Database helpersserver/
├── api/ # API endpoints (/api/*)
│ ├── users/
│ │ ├── index.get.ts → GET /api/users
│ │ ├── index.post.ts → POST /api/users
│ │ ├── [id].get.ts → GET /api/users/:id
│ │ ├── [id].put.ts → PUT /api/users/:id
│ │ └── [id].delete.ts → DELETE /api/users/:id
│ └── health.get.ts → GET /api/health
├── routes/ # 非API路由
│ └── sitemap.xml.get.ts → GET /sitemap.xml
├── middleware/ # 服务端中间件
│ └── auth.ts # 每次请求都会执行
├── plugins/ # Nitro插件
│ └── database.ts # 初始化数据库
└── utils/ # 服务端工具函数
└── db.ts # 数据库辅助方法HTTP Method Suffixes
HTTP方法后缀
| Suffix | HTTP Method |
|---|---|
| GET |
| POST |
| PUT |
| PATCH |
| DELETE |
| All methods |
| 后缀 | HTTP方法 |
|---|---|
| GET |
| POST |
| PUT |
| PATCH |
| DELETE |
| 所有方法 |
When to Load References
何时加载对应参考文档
Load when:
references/server.md- Implementing complex API routes
- Handling authentication and sessions
- Working with cookies and headers
- Building file upload endpoints
- Understanding Nitro internals
Load when:
references/database-patterns.md- Integrating Cloudflare D1 with Drizzle
- Setting up PostgreSQL connections
- Implementing database migrations
- Building query patterns
Load when:
references/websocket-patterns.md- Implementing real-time features
- Building WebSocket endpoints
- Using Durable Objects for state
满足以下场景时加载 :
references/server.md- 实现复杂API路由
- 处理认证与会话
- 操作Cookie和请求头
- 构建文件上传端点
- 了解Nitro内部原理
满足以下场景时加载 :
references/database-patterns.md- 基于Drizzle集成Cloudflare D1
- 配置PostgreSQL连接
- 实现数据库迁移
- 构建查询模式
满足以下场景时加载 :
references/websocket-patterns.md- 实现实时功能
- 构建WebSocket端点
- 使用Durable Objects管理状态
Basic Event Handler
基础事件处理器
typescript
// server/api/users/index.get.ts
export default defineEventHandler(async (event) => {
// Return data (automatically serialized to JSON)
return {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
}
})typescript
// server/api/users/index.get.ts
export default defineEventHandler(async (event) => {
// 返回数据会自动序列化为JSON
return {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
}
})Request Utilities
请求工具函数
URL Parameters
URL参数
typescript
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
message: 'User ID is required'
})
}
return { id }
})typescript
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
message: '用户ID不能为空'
})
}
return { id }
})Query Parameters
查询参数
typescript
// GET /api/users?page=1&limit=10&search=john
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 10
const search = query.search as string | undefined
return { page, limit, search }
})typescript
// GET /api/users?page=1&limit=10&search=john
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 10
const search = query.search as string | undefined
return { page, limit, search }
})Request Body
请求体
typescript
// server/api/users/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validate body
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
message: 'Name and email are required'
})
}
// Create user...
return { success: true, user: { id: 1, ...body } }
})typescript
// server/api/users/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 校验请求体
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
message: '姓名和邮箱不能为空'
})
}
// 创建用户逻辑...
return { success: true, user: { id: 1, ...body } }
})Headers
请求头
typescript
export default defineEventHandler(async (event) => {
// Read headers
const authHeader = getHeader(event, 'authorization')
const contentType = getHeader(event, 'content-type')
// Set response headers
setHeader(event, 'X-Custom-Header', 'value')
setHeader(event, 'Cache-Control', 'max-age=3600')
return { authHeader, contentType }
})typescript
export default defineEventHandler(async (event) => {
// 读取请求头
const authHeader = getHeader(event, 'authorization')
const contentType = getHeader(event, 'content-type')
// 设置响应头
setHeader(event, 'X-Custom-Header', 'value')
setHeader(event, 'Cache-Control', 'max-age=3600')
return { authHeader, contentType }
})Response Utilities
响应工具函数
Setting Status Code
设置状态码
typescript
export default defineEventHandler(async (event) => {
// Set status code
setResponseStatus(event, 201) // Created
return { message: 'Resource created' }
})typescript
export default defineEventHandler(async (event) => {
// 设置状态码
setResponseStatus(event, 201) // 已创建
return { message: '资源创建成功' }
})Redirects
重定向
typescript
export default defineEventHandler(async (event) => {
// Redirect
return sendRedirect(event, '/new-location', 302)
})typescript
export default defineEventHandler(async (event) => {
// 重定向
return sendRedirect(event, '/new-location', 302)
})Error Handling
错误处理
typescript
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await findUser(id)
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
message: `User with ID ${id} not found`
})
}
return user
})typescript
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await findUser(id)
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
message: `ID为${id}的用户不存在`
})
}
return user
})Cookies
Cookies
typescript
export default defineEventHandler(async (event) => {
// Read cookie
const sessionId = getCookie(event, 'session_id')
// Set cookie
setCookie(event, 'session_id', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 1 week
})
// Delete cookie
deleteCookie(event, 'old_cookie')
return { sessionId }
})typescript
export default defineEventHandler(async (event) => {
// 读取Cookie
const sessionId = getCookie(event, 'session_id')
// 设置Cookie
setCookie(event, 'session_id', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 1周
})
// 删除Cookie
deleteCookie(event, 'old_cookie')
return { sessionId }
})Server Middleware
服务端中间件
typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Skip for public routes
const publicRoutes = ['/api/auth/login', '/api/health']
if (publicRoutes.includes(event.path)) {
return // Continue to next handler
}
// Check authentication
const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({
statusCode: 401,
message: 'Authentication required'
})
}
// Verify token and attach user to context
const user = await verifyToken(token)
event.context.user = user
})typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// 公开路由跳过校验
const publicRoutes = ['/api/auth/login', '/api/health']
if (publicRoutes.includes(event.path)) {
return // 继续执行下一个处理器
}
// 校验认证状态
const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({
statusCode: 401,
message: '需要认证权限'
})
}
// 验证Token并将用户信息挂载到上下文
const user = await verifyToken(token)
event.context.user = user
})Accessing Context in Routes
在路由中访问上下文
typescript
// server/api/profile.get.ts
export default defineEventHandler(async (event) => {
// User attached by middleware
const user = event.context.user
if (!user) {
throw createError({ statusCode: 401, message: 'Not authenticated' })
}
return { user }
})typescript
// server/api/profile.get.ts
export default defineEventHandler(async (event) => {
// 用户信息由中间件挂载
const user = event.context.user
if (!user) {
throw createError({ statusCode: 401, message: '未登录' })
}
return { user }
})Database Integration
数据库集成
Cloudflare D1 with Drizzle
基于Drizzle集成Cloudflare D1
typescript
// server/utils/db.ts
import { drizzle } from 'drizzle-orm/d1'
import * as schema from '~/server/database/schema'
export function useDB(event: H3Event) {
const { DB } = event.context.cloudflare.env
return drizzle(DB, { schema })
}
// server/api/users/index.get.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const users = await db.select().from(schema.users).limit(10)
return { users }
})typescript
// server/utils/db.ts
import { drizzle } from 'drizzle-orm/d1'
import * as schema from '~/server/database/schema'
export function useDB(event: H3Event) {
const { DB } = event.context.cloudflare.env
return drizzle(DB, { schema })
}
// server/api/users/index.get.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const users = await db.select().from(schema.users).limit(10)
return { users }
})Schema Definition
Schema定义
typescript
// server/database/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date())
})
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date())
})typescript
// server/database/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date())
})
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date())
})CRUD Operations
CRUD操作
typescript
// server/api/users/index.post.ts
import { users } from '~/server/database/schema'
import { eq } from 'drizzle-orm'
export default defineEventHandler(async (event) => {
const db = useDB(event)
const body = await readBody(event)
// Create
const [user] = await db.insert(users)
.values({ name: body.name, email: body.email })
.returning()
return { user }
})
// server/api/users/[id].put.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const id = getRouterParam(event, 'id')
const body = await readBody(event)
// Update
const [user] = await db.update(users)
.set({ name: body.name })
.where(eq(users.id, Number(id)))
.returning()
if (!user) {
throw createError({ statusCode: 404, message: 'User not found' })
}
return { user }
})
// server/api/users/[id].delete.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const id = getRouterParam(event, 'id')
// Delete
await db.delete(users).where(eq(users.id, Number(id)))
return { success: true }
})typescript
// server/api/users/index.post.ts
import { users } from '~/server/database/schema'
import { eq } from 'drizzle-orm'
export default defineEventHandler(async (event) => {
const db = useDB(event)
const body = await readBody(event)
// 创建
const [user] = await db.insert(users)
.values({ name: body.name, email: body.email })
.returning()
return { user }
})
// server/api/users/[id].put.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const id = getRouterParam(event, 'id')
const body = await readBody(event)
// 更新
const [user] = await db.update(users)
.set({ name: body.name })
.where(eq(users.id, Number(id)))
.returning()
if (!user) {
throw createError({ statusCode: 404, message: '用户不存在' })
}
return { user }
})
// server/api/users/[id].delete.ts
export default defineEventHandler(async (event) => {
const db = useDB(event)
const id = getRouterParam(event, 'id')
// 删除
await db.delete(users).where(eq(users.id, Number(id)))
return { success: true }
})Validation with Zod
使用Zod进行校验
typescript
// server/api/users/index.post.ts
import { z } from 'zod'
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional()
})
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validate
const result = createUserSchema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
message: 'Validation failed',
data: result.error.flatten()
})
}
// Use validated data
const { name, email, age } = result.data
// Create user...
return { success: true }
})typescript
// server/api/users/index.post.ts
import { z } from 'zod'
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional()
})
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 校验
const result = createUserSchema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
message: '参数校验失败',
data: result.error.flatten()
})
}
// 使用校验后的参数
const { name, email, age } = result.data
// 创建用户逻辑...
return { success: true }
})File Uploads
文件上传
typescript
// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
const formData = await readMultipartFormData(event)
if (!formData) {
throw createError({ statusCode: 400, message: 'No file uploaded' })
}
const file = formData.find(f => f.name === 'file')
if (!file) {
throw createError({ statusCode: 400, message: 'File field is required' })
}
// file.filename - Original filename
// file.type - MIME type
// file.data - Buffer with file contents
// Upload to R2 (Cloudflare)
const { R2 } = event.context.cloudflare.env
const key = `uploads/${Date.now()}-${file.filename}`
await R2.put(key, file.data)
return { key, filename: file.filename, type: file.type }
})typescript
// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
const formData = await readMultipartFormData(event)
if (!formData) {
throw createError({ statusCode: 400, message: '未上传文件' })
}
const file = formData.find(f => f.name === 'file')
if (!file) {
throw createError({ statusCode: 400, message: 'file字段不能为空' })
}
// file.filename - 原始文件名
// file.type - MIME类型
// file.data - 存储文件内容的Buffer
// 上传到R2 (Cloudflare)
const { R2 } = event.context.cloudflare.env
const key = `uploads/${Date.now()}-${file.filename}`
await R2.put(key, file.data)
return { key, filename: file.filename, type: file.type }
})Server Utilities
服务端工具函数
typescript
// server/utils/auth.ts
import { H3Event } from 'h3'
export function requireAuth(event: H3Event) {
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
message: 'Authentication required'
})
}
return user
}
export function requireRole(event: H3Event, role: string) {
const user = requireAuth(event)
if (user.role !== role) {
throw createError({
statusCode: 403,
message: 'Insufficient permissions'
})
}
return user
}
// Usage in routes
export default defineEventHandler(async (event) => {
const user = requireAuth(event)
// or
const admin = requireRole(event, 'admin')
})typescript
// server/utils/auth.ts
import { H3Event } from 'h3'
export function requireAuth(event: H3Event) {
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
message: '需要认证权限'
})
}
return user
}
export function requireRole(event: H3Event, role: string) {
const user = requireAuth(event)
if (user.role !== role) {
throw createError({
statusCode: 403,
message: '权限不足'
})
}
return user
}
// 路由中使用示例
export default defineEventHandler(async (event) => {
const user = requireAuth(event)
// 或者
const admin = requireRole(event, 'admin')
})Common Anti-Patterns
常见反模式
Missing Method Suffix
缺少方法后缀
typescript
// WRONG - Handles all methods
// server/api/users.ts
// CORRECT - Explicit method
// server/api/users.get.ts → GET
// server/api/users.post.ts → POSTtypescript
// 错误写法 - 会处理所有请求方法
// server/api/users.ts
// 正确写法 - 显式指定方法
// server/api/users.get.ts → GET
// server/api/users.post.ts → POSTNot Throwing Errors
未正确抛出错误
typescript
// WRONG - Returns error as data
export default defineEventHandler(async (event) => {
const user = await findUser(id)
if (!user) {
return { error: 'Not found' } // 200 status!
}
})
// CORRECT - Throw error
export default defineEventHandler(async (event) => {
const user = await findUser(id)
if (!user) {
throw createError({ statusCode: 404, message: 'Not found' })
}
})typescript
// 错误写法 - 将错误作为普通数据返回
export default defineEventHandler(async (event) => {
const user = await findUser(id)
if (!user) {
return { error: 'Not found' } // 状态码仍为200!
}
})
// 正确写法 - 抛出错误
export default defineEventHandler(async (event) => {
const user = await findUser(id)
if (!user) {
throw createError({ statusCode: 404, message: 'Not found' })
}
})Forgetting Async/Await
忘记添加Async/Await
typescript
// WRONG - Body not awaited
export default defineEventHandler((event) => {
const body = readBody(event) // Returns Promise!
})
// CORRECT
export default defineEventHandler(async (event) => {
const body = await readBody(event)
})typescript
// 错误写法 - 未等待Body读取完成
export default defineEventHandler((event) => {
const body = readBody(event) // 返回的是Promise!
})
// 正确写法
export default defineEventHandler(async (event) => {
const body = await readBody(event)
})Troubleshooting
问题排查
404 on API Routes:
- Ensure file is in (not
server/api/)app/api/ - Check method suffix matches request (for GET)
.get.ts - Verify file extension is
.ts
Body is Empty:
- Ensure not
await readBody(event)readBody(event) - Check Content-Type header is set correctly
- For multipart, use
readMultipartFormData
Middleware Not Running:
- Check file is in
server/middleware/ - Middleware runs for ALL requests unless filtered
D1 Binding Not Found:
- Check wrangler.toml has configured
[[d1_databases]] - Access via
event.context.cloudflare.env.DB
API路由返回404:
- 确认文件放在 目录下(而非
server/api/)app/api/ - 检查方法后缀与请求方法匹配(GET请求对应)
.get.ts - 确认文件扩展名为
.ts
请求体为空:
- 确认使用而非
await readBody(event)readBody(event) - 检查Content-Type请求头配置正确
- 上传文件场景使用
readMultipartFormData
中间件未执行:
- 确认文件放在 目录下
server/middleware/ - 未配置过滤规则的中间件会在所有请求中执行
D1绑定不存在:
- 检查wrangler.toml中是否配置了
[[d1_databases]] - 通过访问D1实例
event.context.cloudflare.env.DB
Related Skills
相关技能
- nuxt-core: Project setup, routing, configuration
- nuxt-data: Composables, data fetching, state
- nuxt-production: Performance, testing, deployment
- cloudflare-d1: D1 database patterns
Version: 4.0.0 | Last Updated: 2025-12-28 | License: MIT
- nuxt-core: 项目初始化、路由、配置
- nuxt-data: 组合式函数、数据获取、状态管理
- nuxt-production: 性能优化、测试、部署
- cloudflare-d1: D1数据库开发模式
版本: 4.0.0 | 最后更新: 2025-12-28 | 许可证: MIT