Skip to content

6. Nuxt 3 服务端程序

server/ 目录用于在应用中注册 API 和服务器处理程序。

每个文件应该默认导出由 defineEventHandler()eventHandler() 定义的函数。

处理程序可以直接返回 JSON 数据、Promise,或使用 event.node.res.end() 发送响应。

ts
// server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: 'world'
  }
})

6.1 服务器路由

路由参数

服务器路由可以使用文件名中括号内的动态参数,例如 /api/hello/[name].ts,并通过 event.context.params 访问。

ts
// server/api/hello/[name].ts
export default defineEventHandler((event) => {
  const name = getRouterParam(event, 'name')
  return `Hello, ${name}!`
})

也可以配合验证器使用:

ts
// server/api/hello/[name].ts
import { z } from 'zod'

const userSchema = z.object({
  name: z.string().default('Guest'),
  email: z.string().email(),
})

export default defineEventHandler(async (event) => {
  const result = await readValidatedBody(event, body => userSchema.safeParse(body))
  // or `.parse` to directly throw an error

  if (!result.success)
    throw result.error.issues

  // User object is validated and typed!
  return result.data
})

匹配 HTTP 方法

可以使用形如 name.[method].ts 的文件名来匹配 HTTP 方法,例如 hello.get.ts

ts
// server/api/hello.get.ts
export default defineEventHandler((event) => {
  return '...'
})

全局捕获路由

使用形如 [...].ts[...slug].ts 文件名来捕获所有路由,其中可以通过 event.context.params.slug 获得 slug 的值。

读取请求体

使用 readBody(event) 或者使用 readValidatedBody(event) 来读取请求体。

$fetch() 一起使用,可以自动包装/解包 JSON 数据。

WARNING

如果在 GET 等不支持请求体的方法中使用 readBody(event),将会抛出错误并返回 405。

读取查询参数

使用 getQuery(event) 或者使用 getValidatedQuery(event) 来读取查询参数。

读取 Cookies

使用 parseCookies(event) 来读取请求中的 Cookies。

错误处理

在每个请求处理程序中,可以使用 createError() 来创建错误响应。

ts
export default defineEventHandler((event) => {
  const id = parseInt(event.context.params.id) as number

  if (!Number.isInteger(id)) {
    throw createError({
      statusCode: 400,
      statusMessage: 'ID should be an integer',
    })
  }
  return 'All good'
})

可以使用 setResponseStatus(event, code) 来设置响应状态码。

配置

可使用 useRuntimeConfig() 来读取运行时配置。

将事件作为参数提供给 useRuntimeConfig() 是可选的,但建议传递它以获取服务器路由在运行时被环境变量覆盖的运行时配置。

ts
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
})

现在,环境变量 NUXT_GITHUB_TOKEN 将覆盖自定义的 config.githubToken 值。

重定向

ts
export default defineEventHandler(async (event) => {
  await sendRedirect(event, '/path/redirect/to', 302)
})

6.2 中间件

服务器中间件在每次请求之前执行,可以用来添加请求头、记录日志、修改响应等。它们在 server/middleware 目录下定义。

基本用法

ts
// server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('请求路径:', event.node.req.url)
})

添加请求头

ts
// server/middleware/headers.ts
export default defineEventHandler((event) => {
  event.node.res.setHeader('X-Custom-Header', 'MyValue')
})

身份验证中间件

ts
// server/middleware/auth.ts
export default defineEventHandler((event) => {
  const token = getHeader(event, 'Authorization')
  
  if (!token) {
    throw createError({
      statusCode: 401,
      message: '未授权访问'
    })
  }
  
  // 设置上下文,供后续处理程序使用
  event.context.auth = { token }
})

中间件执行顺序

中间件按文件名字母顺序执行。如需控制顺序,可以在文件名前加数字前缀,如:

server/middleware/
  1.logger.ts
  2.auth.ts
  3.cors.ts

6.3 插件

服务器插件在应用启动时执行一次,用于扩展 Nitro 服务器功能或执行初始化操作。它们在 server/plugins 目录下定义。

基本用法

ts
// server/plugins/setup.ts
export default defineNitroPlugin((nitroApp) => {
  console.log('服务器启动时执行')
  
  // 添加运行时钩子
  nitroApp.hooks.hook('request', (event) => {
    console.log('收到新请求:', event.path)
  })
})

数据库连接

ts
// server/plugins/mongodb.ts
import { MongoClient } from 'mongodb'

export default defineNitroPlugin(async (nitroApp) => {
  const config = useRuntimeConfig()
  const client = new MongoClient(config.mongoUrl)
  
  await client.connect()
  
  // 将数据库客户端添加到上下文中
  nitroApp.hooks.hook('request', (event) => {
    event.context.db = client.db('myapp')
  })
  
  // 关闭连接
  nitroApp.hooks.hook('close', () => {
    client.close()
  })
})

注册全局工具函数

ts
// server/plugins/utils.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.utils = {
      formatDate: (date: Date) => {
        return date.toISOString()
      },
      generateId: () => {
        return Math.random().toString(36).slice(2)
      }
    }
  })
})

使用插件提供的工具:

ts
// server/api/posts.ts
export default defineEventHandler((event) => {
  const { utils } = event.context
  
  return {
    id: utils.generateId(),
    createdAt: utils.formatDate(new Date())
  }
})

6.4 高级

发送流

实验性 现在可以使用 sendStream(event, stream) 来发送流。

ts
import fs from 'node:fs'
import { sendStream } from 'h3'

export default defineEventHandler((event) => {
  return sendStream(event, fs.createReadStream('/path/to/file'))
})

服务端 K/V 存储

Nitro 集成了 unjs/unstorage,支持在服务端存储 K/V 信息。可安装各种适配器以支持不同环境和中间件。请阅读 官方文档 了解详细信息。

下面以 Redis 为例:

ts
// nuxt.config.ts
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
      }
    }
  }
})

使用:

ts
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 {}
})

WebSocket

建议使用 Socket.IO 等成熟的库来处理 WebSocket,Nuxt 3 可用的库如 nuxt-socket-io