编写插件 #
一、插件基础 #
1.1 最简插件 #
javascript
async function myPlugin(fastify, options) {
fastify.decorate('hello', () => {
return 'Hello World'
})
}
module.exports = myPlugin
使用插件:
javascript
const fastify = require('fastify')()
fastify.register(require('./my-plugin'))
fastify.get('/', async (request, reply) => {
return { message: fastify.hello() }
})
1.2 使用fastify-plugin #
javascript
const fp = require('fastify-plugin')
async function myPlugin(fastify, options) {
fastify.decorate('utility', () => {
return 'Utility function'
})
}
module.exports = fp(myPlugin, {
name: 'my-plugin'
})
1.3 插件选项 #
javascript
async function myPlugin(fastify, options) {
const defaults = {
enabled: true,
timeout: 5000
}
const config = { ...defaults, ...options }
fastify.decorate('config', config)
}
module.exports = myPlugin
二、装饰器模式 #
2.1 装饰Fastify实例 #
javascript
const fp = require('fastify-plugin')
async function databasePlugin(fastify, options) {
const { MongoClient } = require('mongodb')
const client = new MongoClient(options.url)
await client.connect()
const db = client.db(options.database)
fastify.decorate('mongo', client)
fastify.decorate('db', db)
fastify.addHook('onClose', async (instance) => {
await client.close()
})
}
module.exports = fp(databasePlugin, {
name: 'database'
})
2.2 装饰Request #
javascript
const fp = require('fastify-plugin')
async function authPlugin(fastify, options) {
fastify.decorateRequest('user', null)
fastify.addHook('onRequest', async (request, reply) => {
const token = request.headers.authorization
if (token) {
try {
request.user = await verifyToken(token)
} catch (err) {
request.log.warn('Token verification failed')
}
}
})
}
module.exports = fp(authPlugin, {
name: 'auth'
})
2.3 装饰Reply #
javascript
const fp = require('fastify-plugin')
async function responsePlugin(fastify, options) {
fastify.decorateReply('success', function (data) {
return this.send({
success: true,
data,
timestamp: new Date().toISOString()
})
})
fastify.decorateReply('error', function (message, code = 400) {
return this.code(code).send({
success: false,
error: message,
timestamp: new Date().toISOString()
})
})
}
module.exports = fp(responsePlugin, {
name: 'response'
})
2.4 装饰器检查 #
javascript
async function myPlugin(fastify, options) {
if (fastify.hasDecorator('db')) {
throw new Error('Database decorator already exists')
}
if (!fastify.hasRequestDecorator('user')) {
fastify.decorateRequest('user', null)
}
if (!fastify.hasReplyDecorator('success')) {
fastify.decorateReply('success', function (data) {
return this.send({ success: true, data })
})
}
}
三、钩子集成 #
3.1 请求钩子 #
javascript
const fp = require('fastify-plugin')
async function loggingPlugin(fastify, options) {
fastify.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now()
request.log.info({ method: request.method, url: request.url }, 'Request started')
})
fastify.addHook('onResponse', async (request, reply) => {
const duration = Date.now() - request.startTime
request.log.info({
method: request.method,
url: request.url,
statusCode: reply.statusCode,
duration
}, 'Request completed')
})
}
module.exports = fp(loggingPlugin, {
name: 'logging'
})
3.2 认证钩子 #
javascript
const fp = require('fastify-plugin')
async function authPlugin(fastify, options) {
fastify.decorate('authenticate', async function (request, reply) {
const token = request.headers.authorization
if (!token) {
reply.code(401).send({ error: 'No token provided' })
return
}
try {
const decoded = await verifyToken(token)
request.user = decoded
} catch (err) {
reply.code(401).send({ error: 'Invalid token' })
}
})
fastify.decorate('authorize', function (roles) {
return async function (request, reply) {
if (!request.user) {
reply.code(401).send({ error: 'Unauthorized' })
return
}
if (!roles.includes(request.user.role)) {
reply.code(403).send({ error: 'Forbidden' })
}
}
})
}
module.exports = fp(authPlugin, {
name: 'auth'
})
3.3 错误处理钩子 #
javascript
const fp = require('fastify-plugin')
async function errorHandlerPlugin(fastify, options) {
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
reply.code(400).send({
statusCode: 400,
error: 'Validation Error',
message: error.message,
details: error.validation
})
return
}
if (error.statusCode) {
reply.code(error.statusCode).send({
statusCode: error.statusCode,
error: error.name,
message: error.message
})
return
}
fastify.log.error(error)
reply.code(500).send({
statusCode: 500,
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: error.message
})
})
}
module.exports = fp(errorHandlerPlugin, {
name: 'error-handler'
})
四、服务封装 #
4.1 数据库服务 #
javascript
const fp = require('fastify-plugin')
class UserService {
constructor(db) {
this.collection = db.collection('users')
}
async findAll(query = {}) {
return this.collection.find(query).toArray()
}
async findById(id) {
return this.collection.findOne({ _id: id })
}
async create(data) {
const result = await this.collection.insertOne({
...data,
createdAt: new Date()
})
return this.findById(result.insertedId)
}
async update(id, data) {
await this.collection.updateOne(
{ _id: id },
{ $set: { ...data, updatedAt: new Date() } }
)
return this.findById(id)
}
async delete(id) {
const result = await this.collection.deleteOne({ _id: id })
return result.deletedCount > 0
}
}
async function servicesPlugin(fastify, options) {
fastify.decorate('services', {
user: new UserService(fastify.db)
})
}
module.exports = fp(servicesPlugin, {
name: 'services',
dependencies: ['database']
})
4.2 缓存服务 #
javascript
const fp = require('fastify-plugin')
class CacheService {
constructor(redis) {
this.redis = redis
}
async get(key) {
const data = await this.redis.get(key)
return data ? JSON.parse(data) : null
}
async set(key, value, ttl = 3600) {
await this.redis.set(key, JSON.stringify(value), 'EX', ttl)
}
async delete(key) {
await this.redis.del(key)
}
async getOrSet(key, fn, ttl = 3600) {
const cached = await this.get(key)
if (cached) {
return cached
}
const data = await fn()
await this.set(key, data, ttl)
return data
}
}
async function cachePlugin(fastify, options) {
fastify.decorate('cache', new CacheService(fastify.redis))
}
module.exports = fp(cachePlugin, {
name: 'cache',
dependencies: ['redis']
})
4.3 邮件服务 #
javascript
const fp = require('fastify-plugin')
const nodemailer = require('nodemailer')
class EmailService {
constructor(options) {
this.transporter = nodemailer.createTransport(options)
this.from = options.from
}
async send(to, subject, body) {
await this.transporter.sendMail({
from: this.from,
to,
subject,
html: body
})
}
async sendTemplate(to, template, data) {
const body = await this.renderTemplate(template, data)
await this.send(to, data.subject, body)
}
}
async function emailPlugin(fastify, options) {
fastify.decorate('email', new EmailService(options))
}
module.exports = fp(emailPlugin, {
name: 'email'
})
五、配置验证 #
5.1 必需选项 #
javascript
async function myPlugin(fastify, options) {
const required = ['apiKey', 'apiSecret']
for (const key of required) {
if (!options[key]) {
throw new Error(`Missing required option: ${key}`)
}
}
fastify.decorate('api', options)
}
5.2 选项Schema #
javascript
const fp = require('fastify-plugin')
const Ajv = require('ajv')
const optionsSchema = {
type: 'object',
required: ['url', 'database'],
properties: {
url: { type: 'string', format: 'uri' },
database: { type: 'string', minLength: 1 },
poolSize: { type: 'integer', minimum: 1, default: 10 },
timeout: { type: 'integer', minimum: 1000, default: 30000 }
}
}
async function databasePlugin(fastify, options) {
const ajv = new Ajv({ useDefaults: true })
const validate = ajv.compile(optionsSchema)
if (!validate(options)) {
throw new Error(`Invalid options: ${ajv.errorsText(validate.errors)}`)
}
fastify.decorate('db', await connect(options))
}
module.exports = fp(databasePlugin, {
name: 'database'
})
5.3 默认值合并 #
javascript
async function myPlugin(fastify, options) {
const defaults = {
enabled: true,
timeout: 5000,
retries: 3,
logLevel: 'info'
}
const config = { ...defaults, ...options }
fastify.decorate('config', config)
}
六、插件依赖 #
6.1 声明依赖 #
javascript
const fp = require('fastify-plugin')
async function myPlugin(fastify, options) {
const db = fastify.db
const redis = fastify.redis
fastify.decorate('service', new Service(db, redis))
}
module.exports = fp(myPlugin, {
name: 'my-plugin',
dependencies: ['database', 'redis']
})
6.2 可选依赖 #
javascript
async function myPlugin(fastify, options) {
const cache = fastify.hasDecorator('redis')
? new RedisCache(fastify.redis)
: new MemoryCache()
fastify.decorate('cache', cache)
}
七、资源管理 #
7.1 连接管理 #
javascript
const fp = require('fastify-plugin')
async function databasePlugin(fastify, options) {
let connection = null
const connect = async () => {
connection = await createConnection(options)
fastify.decorate('db', connection)
fastify.log.info('Database connected')
}
const disconnect = async () => {
if (connection) {
await connection.close()
fastify.log.info('Database disconnected')
}
}
await connect()
fastify.addHook('onClose', async () => {
await disconnect()
})
fastify.addHook('onReady', async () => {
if (!connection) {
await connect()
}
})
}
module.exports = fp(databasePlugin, {
name: 'database'
})
7.2 连接池 #
javascript
const fp = require('fastify-plugin')
class ConnectionPool {
constructor(options) {
this.options = options
this.pool = []
this.maxSize = options.maxSize || 10
}
async acquire() {
if (this.pool.length > 0) {
return this.pool.pop()
}
return this.create()
}
async release(connection) {
if (this.pool.length < this.maxSize) {
this.pool.push(connection)
} else {
await connection.close()
}
}
async create() {
return createConnection(this.options)
}
async closeAll() {
for (const conn of this.pool) {
await conn.close()
}
this.pool = []
}
}
async function poolPlugin(fastify, options) {
const pool = new ConnectionPool(options)
fastify.decorate('pool', pool)
fastify.addHook('onClose', async () => {
await pool.closeAll()
})
}
module.exports = fp(poolPlugin, {
name: 'pool'
})
八、插件测试 #
8.1 基本测试 #
javascript
const { test } = require('node:test')
const assert = require('node:assert')
const Fastify = require('fastify')
const myPlugin = require('./my-plugin')
test('plugin registers correctly', async (t) => {
const fastify = Fastify()
await fastify.register(myPlugin, { option: 'value' })
await fastify.ready()
assert.ok(fastify.hasDecorator('utility'))
assert.strictEqual(fastify.utility(), 'Hello World')
await fastify.close()
})
8.2 钩子测试 #
javascript
test('plugin hooks work correctly', async (t) => {
const fastify = Fastify()
await fastify.register(myPlugin)
fastify.get('/test', async (request, reply) => {
return { user: request.user }
})
await fastify.ready()
const response = await fastify.inject({
method: 'GET',
url: '/test',
headers: { authorization: 'valid-token' }
})
assert.strictEqual(response.statusCode, 200)
await fastify.close()
})
8.3 错误测试 #
javascript
test('plugin throws on missing required option', async (t) => {
const fastify = Fastify()
await assert.rejects(
async () => {
await fastify.register(myPlugin, {})
await fastify.ready()
},
{ message: /Missing required option/ }
)
await fastify.close()
})
九、发布插件 #
9.1 package.json #
json
{
"name": "fastify-my-plugin",
"version": "1.0.0",
"description": "A Fastify plugin for...",
"main": "index.js",
"scripts": {
"test": "node --test",
"lint": "eslint ."
},
"keywords": [
"fastify",
"plugin"
],
"peerDependencies": {
"fastify": "4.x"
},
"dependencies": {
"fastify-plugin": "^4.0.0"
},
"devDependencies": {
"fastify": "^4.0.0"
}
}
9.2 README模板 #
markdown
# fastify-my-plugin
A Fastify plugin for...
## Installation
```bash
npm install fastify-my-plugin
Usage #
javascript
const fastify = require('fastify')()
fastify.register(require('fastify-my-plugin'), {
option: 'value'
})
Options #
| Option | Type | Default | Description |
|---|---|---|---|
| option | string | - | Description |
License #
MIT
text
## 十、最佳实践
### 10.1 插件命名
```javascript
module.exports = fp(myPlugin, {
name: '@myorg/fastify-mysql'
})
10.2 错误处理 #
javascript
async function myPlugin(fastify, options) {
try {
const resource = await createResource(options)
fastify.decorate('resource', resource)
} catch (err) {
fastify.log.error('Failed to initialize plugin:', err)
throw err
}
}
10.3 日志记录 #
javascript
async function myPlugin(fastify, options) {
fastify.log.info('Initializing plugin...')
const resource = await createResource(options)
fastify.decorate('resource', resource)
fastify.log.info('Plugin initialized successfully')
}
十一、总结 #
本章我们学习了:
- 插件基础:最简插件、fastify-plugin、插件选项
- 装饰器模式:装饰实例、Request、Reply
- 钩子集成:请求钩子、认证钩子、错误处理
- 服务封装:数据库服务、缓存服务、邮件服务
- 配置验证:必需选项、选项Schema、默认值
- 插件依赖:声明依赖、可选依赖
- 资源管理:连接管理、连接池
- 插件测试:基本测试、钩子测试、错误测试
- 发布插件:package.json、README
- 最佳实践:命名、错误处理、日志记录
接下来让我们学习插件作用域与封装!
最后更新:2026-03-28