Nuxt.js服务端API #

一、Nitro概述 #

Nuxt 3 使用 Nitro 作为服务端引擎,提供了强大的服务端 API 开发能力。Nitro 支持:

  • 文件系统路由
  • 自动导入工具函数
  • 多种部署目标
  • 热模块替换

二、API路由 #

2.1 创建API路由 #

server/api/hello.ts

typescript
export default defineEventHandler((event) => {
  return {
    message: 'Hello, World!'
  }
})

访问:GET /api/hello

2.2 HTTP方法 #

server/api/users.ts

typescript
export default defineEventHandler(async (event) => {
  const method = getMethod(event)
  
  switch (method) {
    case 'GET':
      return await getUsers()
    case 'POST':
      return await createUser(event)
    default:
      throw createError({
        statusCode: 405,
        message: 'Method Not Allowed'
      })
  }
})

async function getUsers() {
  return { users: [] }
}

async function createUser(event) {
  const body = await readBody(event)
  return { user: body }
}

2.3 特定方法路由 #

server/api/users.get.ts

typescript
export default defineEventHandler(() => {
  return { users: [] }
})

server/api/users.post.ts

typescript
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { user: body }
})

2.4 动态路由 #

server/api/users/[id].ts

typescript
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id')
  
  return {
    id,
    name: `User ${id}`
  }
})

2.5 Catch-all路由 #

server/api/articles/[...slug].ts

typescript
export default defineEventHandler((event) => {
  const slug = getRouterParam(event, 'slug')
  
  return {
    path: Array.isArray(slug) ? slug.join('/') : slug
  }
})

三、请求处理 #

3.1 读取请求体 #

typescript
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  
  return { received: body }
})

3.2 读取查询参数 #

typescript
export default defineEventHandler((event) => {
  const query = getQuery(event)
  
  return {
    page: query.page || 1,
    limit: query.limit || 10
  }
})

3.3 读取路由参数 #

typescript
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id')
  const slug = getRouterParam(event, 'slug')
  
  return { id, slug }
})

3.4 读取请求头 #

typescript
export default defineEventHandler((event) => {
  const authorization = getHeader(event, 'authorization')
  const contentType = getHeader(event, 'content-type')
  
  return { authorization, contentType }
})

3.5 读取Cookies #

typescript
export default defineEventHandler((event) => {
  const token = getCookie(event, 'token')
  
  return { authenticated: !!token }
})

四、响应处理 #

4.1 设置响应头 #

typescript
export default defineEventHandler((event) => {
  setHeader(event, 'Content-Type', 'application/json')
  setHeader(event, 'X-Custom-Header', 'value')
  
  return { message: 'Hello' }
})

4.2 设置Cookies #

typescript
export default defineEventHandler((event) => {
  setCookie(event, 'token', 'jwt-token', {
    httpOnly: true,
    secure: true,
    maxAge: 60 * 60 * 24 * 7
  })
  
  return { success: true }
})

4.3 设置状态码 #

typescript
export default defineEventHandler((event) => {
  setResponseStatus(event, 201)
  
  return { created: true }
})

4.4 重定向 #

typescript
export default defineEventHandler((event) => {
  return sendRedirect(event, '/new-location', 302)
})

4.5 流式响应 #

typescript
export default defineEventHandler(async (event) => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue('Hello, ')
      controller.enqueue('World!')
      controller.close()
    }
  })
  
  return sendStream(event, stream)
})

五、错误处理 #

5.1 抛出错误 #

typescript
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id')
  
  if (!id) {
    throw createError({
      statusCode: 400,
      message: 'ID is required'
    })
  }
  
  return { id }
})

5.2 自定义错误 #

typescript
class NotFoundError extends Error {
  statusCode = 404
}

export default defineEventHandler((event) => {
  throw new NotFoundError('Resource not found')
})

5.3 错误处理中间件 #

server/middleware/error.ts

typescript
export default defineEventHandler(async (event) => {
  try {
    return await event.handler(event)
  } catch (error: any) {
    console.error('Error:', error)
    
    if (error.statusCode) {
      throw error
    }
    
    throw createError({
      statusCode: 500,
      message: 'Internal Server Error'
    })
  }
})

六、中间件 #

6.1 服务端中间件 #

server/middleware/auth.ts

typescript
export default defineEventHandler((event) => {
  const token = getHeader(event, 'authorization')
  
  if (!token) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized'
    })
  }
  
  event.context.user = verifyToken(token)
})

6.2 在API中使用中间件数据 #

typescript
export default defineEventHandler((event) => {
  const user = event.context.user
  
  return { user }
})

6.3 条件中间件 #

typescript
export default defineEventHandler((event) => {
  const url = getRequestURL(event)
  
  if (url.pathname.startsWith('/api/admin')) {
    const user = event.context.user
    
    if (user?.role !== 'admin') {
      throw createError({
        statusCode: 403,
        message: 'Forbidden'
      })
    }
  }
})

七、数据库集成 #

7.1 使用Prisma #

安装:

bash
pnpm add prisma @prisma/client

server/utils/db.ts

typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default prisma

server/api/users.get.ts

typescript
import prisma from '~/server/utils/db'

export default defineEventHandler(async () => {
  const users = await prisma.user.findMany()
  
  return { users }
})

7.2 使用MongoDB #

bash
pnpm add mongodb

server/utils/db.ts

typescript
import { MongoClient } from 'mongodb'

let client: MongoClient

export const getDb = async () => {
  if (!client) {
    client = new MongoClient(process.env.MONGODB_URI!)
    await client.connect()
  }
  
  return client.db('myapp')
}

server/api/users.get.ts

typescript
import { getDb } from '~/server/utils/db'

export default defineEventHandler(async () => {
  const db = await getDb()
  const users = await db.collection('users').find().toArray()
  
  return { users }
})

八、认证系统 #

8.1 登录API #

server/api/auth/login.post.ts

typescript
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event)
  
  const user = await findUserByEmail(email)
  
  if (!user || !await bcrypt.compare(password, user.password)) {
    throw createError({
      statusCode: 401,
      message: 'Invalid credentials'
    })
  }
  
  const token = jwt.sign(
    { userId: user.id },
    process.env.JWT_SECRET!,
    { expiresIn: '7d' }
  )
  
  setCookie(event, 'token', token, {
    httpOnly: true,
    secure: true,
    maxAge: 60 * 60 * 24 * 7
  })
  
  return {
    user: {
      id: user.id,
      email: user.email,
      name: user.name
    },
    token
  }
})

8.2 认证中间件 #

server/middleware/auth.ts

typescript
import jwt from 'jsonwebtoken'

export default defineEventHandler((event) => {
  const token = getCookie(event, 'token') || 
                getHeader(event, 'authorization')?.replace('Bearer ', '')
  
  if (!token) {
    return
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!)
    event.context.user = decoded
  } catch (error) {
    deleteCookie(event, 'token')
  }
})

8.3 受保护的路由 #

server/api/user/profile.get.ts

typescript
export default defineEventHandler((event) => {
  const user = event.context.user
  
  if (!user) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized'
    })
  }
  
  return { user }
})

九、文件上传 #

9.1 处理文件上传 #

server/api/upload.post.ts

typescript
import { writeFile } from 'fs/promises'
import { join } from 'path'

export default defineEventHandler(async (event) => {
  const files = await readMultipartFormData(event)
  
  if (!files) {
    throw createError({
      statusCode: 400,
      message: 'No files uploaded'
    })
  }
  
  const uploadedFiles = []
  
  for (const file of files) {
    if (file.type?.startsWith('image/')) {
      const filename = `${Date.now()}-${file.filename}`
      const filepath = join(process.cwd(), 'public', 'uploads', filename)
      
      await writeFile(filepath, file.data)
      
      uploadedFiles.push({
        filename,
        url: `/uploads/${filename}`
      })
    }
  }
  
  return { files: uploadedFiles }
})

十、完整示例 #

10.1 RESTful API #

server/api/posts/index.ts

typescript
export default defineEventHandler(async (event) => {
  const method = getMethod(event)
  
  switch (method) {
    case 'GET':
      return await getPosts(event)
    case 'POST':
      return await createPost(event)
    default:
      throw createError({
        statusCode: 405,
        message: 'Method Not Allowed'
      })
  }
})

async function getPosts(event) {
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = Number(query.limit) || 10
  
  const posts = await prisma.post.findMany({
    skip: (page - 1) * limit,
    take: limit,
    include: {
      author: {
        select: { id: true, name: true }
      }
    }
  })
  
  const total = await prisma.post.count()
  
  return {
    data: posts,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit)
    }
  }
}

async function createPost(event) {
  const user = event.context.user
  
  if (!user) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized'
    })
  }
  
  const body = await readBody(event)
  
  const post = await prisma.post.create({
    data: {
      title: body.title,
      content: body.content,
      authorId: user.userId
    }
  })
  
  setResponseStatus(event, 201)
  
  return { post }
}

server/api/posts/[id].ts

typescript
export default defineEventHandler(async (event) => {
  const method = getMethod(event)
  const id = getRouterParam(event, 'id')
  
  switch (method) {
    case 'GET':
      return await getPost(id)
    case 'PUT':
      return await updatePost(event, id)
    case 'DELETE':
      return await deletePost(event, id)
    default:
      throw createError({
        statusCode: 405,
        message: 'Method Not Allowed'
      })
  }
})

async function getPost(id: string) {
  const post = await prisma.post.findUnique({
    where: { id },
    include: {
      author: {
        select: { id: true, name: true }
      }
    }
  })
  
  if (!post) {
    throw createError({
      statusCode: 404,
      message: 'Post not found'
    })
  }
  
  return { post }
}

async function updatePost(event, id: string) {
  const user = event.context.user
  const body = await readBody(event)
  
  const post = await prisma.post.findUnique({
    where: { id }
  })
  
  if (!post) {
    throw createError({
      statusCode: 404,
      message: 'Post not found'
    })
  }
  
  if (post.authorId !== user.userId) {
    throw createError({
      statusCode: 403,
      message: 'Forbidden'
    })
  }
  
  const updated = await prisma.post.update({
    where: { id },
    data: body
  })
  
  return { post: updated }
}

async function deletePost(event, id: string) {
  const user = event.context.user
  
  const post = await prisma.post.findUnique({
    where: { id }
  })
  
  if (!post) {
    throw createError({
      statusCode: 404,
      message: 'Post not found'
    })
  }
  
  if (post.authorId !== user.userId) {
    throw createError({
      statusCode: 403,
      message: 'Forbidden'
    })
  }
  
  await prisma.post.delete({
    where: { id }
  })
  
  return { success: true }
}

十一、总结 #

本章介绍了 Nuxt.js 服务端 API 开发:

  • 使用 Nitro 创建 API 路由
  • 处理请求和响应
  • 错误处理和中间件
  • 数据库集成
  • 认证系统实现
  • 文件上传处理

服务端 API 能力让 Nuxt.js 成为真正的全栈框架,下一章我们将学习缓存策略。

最后更新:2026-03-28