nuxt-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Nuxt 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 helpers
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/                   # 非API路由
│   └── sitemap.xml.get.ts    → GET  /sitemap.xml
├── middleware/               # 服务端中间件
│   └── auth.ts               # 每次请求都会执行
├── plugins/                  # Nitro插件
│   └── database.ts           # 初始化数据库
└── utils/                    # 服务端工具函数
    └── db.ts                 # 数据库辅助方法

HTTP Method Suffixes

HTTP方法后缀

SuffixHTTP Method
.get.ts
GET
.post.ts
POST
.put.ts
PUT
.patch.ts
PATCH
.delete.ts
DELETE
.ts
All methods
后缀HTTP方法
.get.ts
GET
.post.ts
POST
.put.ts
PUT
.patch.ts
PATCH
.delete.ts
DELETE
.ts
所有方法

When to Load References

何时加载对应参考文档

Load
references/server.md
when:
  • Implementing complex API routes
  • Handling authentication and sessions
  • Working with cookies and headers
  • Building file upload endpoints
  • Understanding Nitro internals
Load
references/database-patterns.md
when:
  • Integrating Cloudflare D1 with Drizzle
  • Setting up PostgreSQL connections
  • Implementing database migrations
  • Building query patterns
Load
references/websocket-patterns.md
when:
  • 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  → POST
typescript
// 错误写法 - 会处理所有请求方法
// server/api/users.ts

// 正确写法 - 显式指定方法
// server/api/users.get.ts   → GET
// server/api/users.post.ts  → POST

Not 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
    server/api/
    (not
    app/api/
    )
  • Check method suffix matches request (
    .get.ts
    for GET)
  • Verify file extension is
    .ts
Body is Empty:
  • Ensure
    await readBody(event)
    not
    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
    [[d1_databases]]
    configured
  • 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]]
  • 通过
    event.context.cloudflare.env.DB
    访问D1实例

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