Skip to content

服务插件

server/plugins/ 下的文件在 Nitro 服务器启动时执行一次,用于初始化逻辑。与服务中间件不同,插件不是每个请求都执行。

插件 vs 中间件 vs 工具函数

概念执行时机作用示例
服务插件服务器启动时(一次)初始化连接、注册钩子数据库连接、Redis 初始化
服务中间件每个请求时请求拦截JWT 验证、CORS、限流
工具函数被调用时可复用逻辑JWT 签名、密码加密

什么时候用服务插件?

  • 需要在服务启动时初始化的连接(数据库、Redis、消息队列)
  • 需要注册全局钩子(请求日志、错误监控)
  • 需要启动定时任务(数据清理、缓存预热)

简单记忆

启动时做一次的事 → 插件;每个请求做的事 → 中间件;需要时调用的逻辑 → 工具函数。

基本用法

ts
// server/plugins/db.ts
import { drizzle } from 'drizzle-orm/node-postgres'

export default defineNitroPlugin(async (nitroApp) => {
  const db = await drizzle(process.env.DATABASE_URL!)

  // 将 db 注入到每个请求的 context 中
  nitroApp.hooks.hook('request', (event) => {
    event.context.db = db
  })

  console.log('Database connected')
})

defineNitroPlugin 的原理

  1. Nitro 启动时,扫描 server/plugins/ 目录
  2. 按文件名字母顺序执行每个插件
  3. 插件接收 nitroApp 实例,可以注册钩子、访问存储等

INFO

插件只在服务端运行——客户端代码中不会执行

常见用途

数据库初始化

ts
// server/plugins/database.ts
import { drizzle } from 'drizzle-orm/node-postgres'

export default defineNitroPlugin(async (nitroApp) => {
  const db = drizzle(process.env.DATABASE_URL!)

  // 注入到每个请求
  nitroApp.hooks.hook('request', (event) => {
    event.context.db = db
  })

  // 也可以挂载到 nitroApp 上
  nitroApp.db = db

  console.log('Database connected')
})

注入到 event.context vs 挂载到 nitroApp

  • event.context.db:每个请求可访问,推荐方式
  • nitroApp.db:全局单例,适合存储连接池等

Redis 初始化

ts
// server/plugins/redis.ts
import { createClient } from 'redis'

export default defineNitroPlugin(async () => {
  const client = createClient({ url: process.env.REDIS_URL })
  await client.connect()

  // 挂载到 Nitro 存储
  const storage = useStorage()
  storage.mount('redis', redisDriver({ client }))

  console.log('Redis connected')
})

定时任务

ts
// server/plugins/cron.ts
export default defineNitroPlugin(() => {
  // 每小时清理过期数据
  setInterval(async () => {
    await $fetch('/api/internal/cleanup', { method: 'POST' })
  }, 60 * 60 * 1000)

  // 每 5 分钟预热缓存
  setInterval(async () => {
    await $fetch('/api/internal/warm-cache', { method: 'POST' })
  }, 5 * 60 * 1000)

  console.log('Cron jobs scheduled')
})

定时任务的注意事项

  • setInterval 在 Node.js 环境中有效,但在 Serverless 环境中不可用
  • 如果部署到 Serverless(如 Vercel、Cloudflare Workers),定时任务需要用平台提供的 Cron Triggers
  • 推荐在定时任务中调用内部 API,而不是直接执行逻辑——这样更容易调试

Nitro 钩子

ts
// server/plugins/hooks.ts
export default defineNitroPlugin((nitroApp) => {
  // 请求钩子——每个请求执行
  nitroApp.hooks.hook('request', (event) => {
    console.log('请求:', event.path)
  })

  // 错误钩子——处理未捕获的错误
  nitroApp.hooks.hook('error', async (error, event) => {
    console.error('服务端错误:', error.message)
    // 可以发送到错误监控(如 Sentry)
  })

  // 响应钩子——每个响应后执行
  nitroApp.hooks.hook('afterResponse', async (event) => {
    const duration = Date.now() - event.context.startTime
    console.log(`响应: ${event.path} - ${duration}ms`)
  })

  // 关闭钩子——服务器关闭时执行
  nitroApp.hooks.hook('close', () => {
    console.log('服务器关闭')
    // 清理资源(关闭数据库连接等)
  })
})

Nitro 钩子的完整列表

钩子触发时机参数
request每个请求到达时event
error未捕获的错误时error, event
afterResponse响应发送后event
close服务器关闭时

请求计时插件

ts
// server/plugins/timing.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.startTime = Date.now()
  })

  nitroApp.hooks.hook('afterResponse', (event) => {
    const duration = Date.now() - event.context.startTime
    setResponseHeader(event, 'X-Response-Time', `${duration}ms`)
  })
})

插件执行顺序

按文件名字母顺序执行,可以用数字前缀控制:

text
server/plugins/
├── 01.database.ts    → 先初始化数据库(其他插件可能依赖)
├── 02.redis.ts       → 再初始化 Redis
└── 03.cron.ts        → 最后启动定时任务(依赖数据库和 Redis)

执行顺序的重要性

  • 如果插件 B 依赖插件 A 的初始化结果,A 必须先执行
  • 使用数字前缀可以明确控制顺序
  • 异步插件会等待 async 函数完成后再执行下一个

实战——完整的认证 + 数据库插件组合

ts
// server/plugins/01.database.ts
import { drizzle } from 'drizzle-orm/node-postgres'

export default defineNitroPlugin(async (nitroApp) => {
  const db = drizzle(process.env.DATABASE_URL!)
  nitroApp.hooks.hook('request', (event) => {
    event.context.db = db
  })
})
ts
// server/plugins/02.auth.ts
export default defineNitroPlugin((nitroApp) => {
  // 将用户信息注入到请求上下文
  nitroApp.hooks.hook('request', (event) => {
    const token = getRequestHeader(event, 'authorization')
    if (token) {
      try {
        event.context.user = verifyToken(token.replace('Bearer ', ''))
      } catch {
        // token 无效,不设置 user
      }
    }
  })
})
ts
// server/plugins/03.logging.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.startTime = Date.now()
  })

  nitroApp.hooks.hook('afterResponse', (event) => {
    const duration = Date.now() - event.context.startTime
    const userId = event.context.user?.id || 'anonymous'
    console.log(`[${event.method}] ${event.path} - ${duration}ms - user:${userId}`)
  })
})

注意事项

  1. 只在服务端运行:服务插件不会在客户端执行,不要引用客户端代码
  2. 启动时执行一次:不是每个请求都执行,适合初始化逻辑
  3. 异步支持:可以使用 async/await,Nitro 会等待异步操作完成
  4. 不要返回值:插件用于初始化,不需要返回数据
  5. Serverless 兼容性setInterval 等长连接功能在 Serverless 环境中不可用

知识脉络

text
API 路由 → 服务路由 → 服务中间件 → 你在这里:服务插件

                                    ├─→ 下一步:工具函数

                                    └─→ 相关:核心概念 → Nitro(服务引擎原理)

基于 Nuxt 4 官方文档整理编写