服务插件
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 的原理
- Nitro 启动时,扫描
server/plugins/目录 - 按文件名字母顺序执行每个插件
- 插件接收
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}`)
})
})注意事项
- 只在服务端运行:服务插件不会在客户端执行,不要引用客户端代码
- 启动时执行一次:不是每个请求都执行,适合初始化逻辑
- 异步支持:可以使用
async/await,Nitro 会等待异步操作完成 - 不要返回值:插件用于初始化,不需要返回数据
- Serverless 兼容性:
setInterval等长连接功能在 Serverless 环境中不可用
知识脉络
text
API 路由 → 服务路由 → 服务中间件 → 你在这里:服务插件
│
├─→ 下一步:工具函数
│
└─→ 相关:核心概念 → Nitro(服务引擎原理)