Fastify 数据库接入实战指南官方数据库插件、自定义连接插件与 SQL 迁移【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastifyFastify 将 Web 框架与数据库彻底解耦database agnostic任何 Node.js 数据库驱动都能通过插件化方式接入框架。本文以 docs/Guides/Database.md 为主线系统讲解 Fastify 官方维护的 MySQL、Postgres、Redis、MongoDB 连接插件用法并深入 Fastify 源码揭示register、decorate、onClose与封装encapsulation机制最终教会你两类核心实战能力为数据库驱动或数据库库ORM/Query Builder编写可复用的 Fastify 插件以及用 Postgrator 把数据库迁移融入 Fastify 应用的开发与发布流程。前置认知Fastify 的数据库哲学Fastify 本身不绑定任何数据库它的核心定位是“Fast and low overhead web framework”。在 Fastify 生态中数据库能力全部由插件提供官方在 Fastify 组织内维护了若干连接插件覆盖主流关系型与非关系型引擎。这一点对本指南至关重要如果你的目标数据库暂时没有官方或社区插件也完全不影响使用。因为 Fastify 只是提供一个插件封装框架你可以参考本指南中几个官方插件的写法为自己选用的数据库引擎编写一个等同的插件本文后续“编写数据库引擎插件”一节会手把手演示。另外如果你打算自己动手写插件请先通读官方插件编写指南docs/Guides/Plugins-Guide.md其中详细介绍了register、decorate、hooks 与封装模型。下面的示例都建立在fastify.register(...)与装饰器之上建议配合阅读。官方维护的四大数据库连接插件以下四个插件的连接对象在被register加载后会以装饰器的形式挂到 Fastify 实例上因此路由处理器内可以直接通过fastify.mysql、fastify.pg、fastify.redis、fastify.mongo访问连接池/客户端。MySQLfastify/mysql安装插件npm i fastify/mysql注册并查询的基本用法const fastify require(fastify)() fastify.register(require(fastify/mysql), { connectionString: mysql://rootlocalhost/mysql }) fastify.get(/user/:id, function(req, reply) { fastify.mysql.query( SELECT id, username, hash, salt FROM users WHERE id?, [req.params.id], function onResult (err, result) { reply.send(err || result) } ) }) fastify.listen({ port: 3000 }, err { if (err) throw err console.log(server listening on ${fastify.server.address().port}) })要点说明connectionString使用标准 MySQL URL 格式亦可通过 host/port/user/password 等独立字段组合传入。fastify.mysql.query(sql, params, callback)使用?占位符传参SQL 注入防护由驱动层完成。req.params.id来自 URL 路径参数与 Fastify 路由定义中的:id对应。查询是异步回调风格回调中直接reply.send(err || result)是 Fastify 的惯例——若err存在则以错误响应返回。生产环境请勿使用本文档里的 root 空密码连接串且示例密码仅作演示。Postgresfastify/postgresPostgres 需要同时安装pgnode-postgres 驱动与 Fastify 封装插件npm i pg fastify/postgres用法const fastify require(fastify)() fastify.register(require(fastify/postgres), { connectionString: postgres://postgreslocalhost/postgres }) fastify.get(/user/:id, function (req, reply) { fastify.pg.query( SELECT id, username, hash, salt FROM users WHERE id$1, [req.params.id], function onResult (err, result) { reply.send(err || result) } ) }) fastify.listen({ port: 3000 }, err { if (err) throw err console.log(server listening on ${fastify.server.address().port}) })与 MySQL 示例最大的区别是占位符语法Postgres 使用$1、$2这样的位置参数而不是?。这里fastify.pg暴露的是 pg 的连接池实例pool因此同样可以调用pool.connect()获取单条连接以支撑事务。Redisfastify/redis安装npm i fastify/redisRedis 插件既支持独立 host 参数也支持完整的urlredis:// 协议use strict const fastify require(fastify)() // 方式一host 简写 fastify.register(require(fastify/redis), { host: 127.0.0.1 }) // 方式二完整连接串可携带其它 redis 选项 fastify.register(require(fastify/redis), { url: redis://127.0.0.1, /* other redis options */ }) fastify.get(/foo, function (req, reply) { const { redis } fastify redis.get(req.query.key, (err, val) { reply.send(err || val) }) }) fastify.post(/foo, function (req, reply) { const { redis } fastify redis.set(req.body.key, req.body.value, (err) { reply.send(err || { status: ok }) }) }) fastify.listen({ port: 3000 }, err { if (err) throw err console.log(server listening on ${fastify.server.address().port}) })这里通过解构const { redis } fastify取出装饰器等价于fastify.redis。redis.get/redis.set均为回调风格其中redis.set(key, value)一旦成功reply.send返回{ status: ok }作为业务结果。连接生命周期注意事项默认情况下fastify/redis不会在 Fastify 服务关闭时关闭客户端连接。如果你希望服务close时主动释放 Redis 连接避免进程无法正常退出需要显式传入已创建的client并开启closeClient: truefastify.register(require(fastify/redis), { client: redis, closeClient: true })这与下文讨论的“数据库插件普遍需要在onClosehook 中销毁连接”是同一思想。MongoDBfastify/mongodb安装npm i fastify/mongodb用法const fastify require(fastify)() fastify.register(require(fastify/mongodb), { // force to close the mongodb connection when app stopped // the default value is false forceClose: true, url: mongodb://mongo/mydb }) fastify.get(/user/:id, async function (req, reply) { // Or this.mongo.client.db(mydb).collection(users) const users this.mongo.db.collection(users) // if the id is an ObjectId format, you need to create a new ObjectId const id this.mongo.ObjectId(req.params.id) try { const user await users.findOne({ id }) return user } catch (err) { return err } }) fastify.listen({ port: 3000 }, err { if (err) throw err })与前面几个插件不同Mongo 示例展示了async handler this上下文的写法url指定 MongoDB 连接串forceClose: true表示应用停止时强制关闭 MongoDB 连接默认false。在进程生命周期管理严格的场景例如测试、serverless、容器优雅退出建议开启。路由处理器是function关键字声明的普通函数因此this指向当前 Fastify 封装实例this.mongo等价于fastify.mongo若改用箭头函数则拿不到this请使用闭包中的fastify。this.mongo.db.collection(users)直接取默认库注释里的等价写法this.mongo.client.db(mydb)用于需要显式指定库名的情况。_id若为 ObjectId 格式需先用this.mongo.ObjectId(...)包装后查询。async handler 中可直接return结果或错误无需手动reply.send。数据库插件背后的 Fastify 机制要理解为什么数据库连接能直接以fastify.xxx出现、又为什么数据库插件几乎都要处理连接关闭需要弄清 Fastify 的三块基石register封装、decorate装饰器与 hooks。register 与封装模型在 Fastify 中路由、工具、数据库连接等一切皆插件。加载任何插件都通过统一的registerAPI 完成它会创建一个新的 Fastify 上下文——这意味着在插件内部对实例做的一切修改不会泄漏到祖先上下文这一特性即“封装”。封装的核心好处在于隔离应用可以安全地按模块组织路由和功能而不必担心兄弟模块之间的命名冲突或隐式依赖。但同时它也带来约束在某个register的上下文中用decorate添加的属性只有该上下文及其子上下文可见。因此官方数据库插件一般都配合fastify-plugin使用见下一小节把连接装饰器提升到应用根部。数据库驱动的接入天然是异步引导连接建立需要时间而decorate是同步 API。插件化是解决该问题的标准姿势把“建立连接 装饰到实例”封装进一个函数再由 Fastify 在.listen()、.inject()或.ready()触发后按图加载从而支持异步就绪。从本仓库 lib/plugin-utils.js 的实现可以看出插件在加载时会经历版本检查checkVersion、装饰器依赖检查checkDecorators、插件依赖检查checkDependencies与skip-override判定等一系列校验最终交由 avvio 图执行器统一调度。decorate把连接挂到实例上装饰器由 lib/decorate.js 中的decorateFastifydecorate.add实现。其核心逻辑很简单若实例上已存在同名属性则抛出FST_ERR_DEC_ALREADY_PRESENT支持 getter/setter 形式与普通值形式支持声明依赖dependencies数组缺失依赖会抛错若应用已启动started状态再调用装饰器会抛FST_ERR_DEC_AFTER_START——所以装饰器必须在启动前声明。数据库插件正是这样工作的插件函数内调用fastify.decorate(mysql, conn)或fastify.pg、fastify.redis、fastify.mongo等此后所有路由即可通过fastify.mysql访问同一连接。decorate还可用于fastify.decorateRequest与fastify.decorateReply为请求/响应对象挂载方法这在封装自定义查询工具时同样常用。需要访问request/reply实例内部状态的方法请使用function关键字而非箭头函数。onClose优雅关闭数据库连接框架在 fastify.js 中定义了生命周期事件其中onClose用于在应用关闭时执行清理。凡是占用外部资源数据库连接、Redis 客户端等的插件标准做法都是注册onClosehook 并销毁连接——这就是本指南中 Redis 插件closeClient: true、Mongo 插件forceClose: true之所以存在的原因也是下面自定义插件示例反复出现fastify.addHook(onClose, ...)的原因。为数据库库ORM / Query Builder编写插件数据库“库”是介于应用与原生驱动之间的抽象层典型代表包括 Knex、Prisma、TypeORM。你可以把这类库的实例化也封装成插件让fastify.knex或任意命名在全局可用。官方文档以 Knex 为例给出完整模板use strict const fp require(fastify-plugin) const knex require(knex) function knexPlugin(fastify, options, done) { if(!fastify.knex) { const knex knex(options) fastify.decorate(knex, knex) fastify.addHook(onClose, (fastify, done) { if (fastify.knex knex) { fastify.knex.destroy(done) } }) } done() } export default fp(knexPlugin, { name: fastify-knex-example })逐段拆解fp(knexPlugin, ...)是关键。fp即fastify-plugin模块它通过给函数打上特殊标记源码 lib/plugin-utils.js 中的Symbol.for(skip-override)告知 Fastify“跳过封装”使fastify.decorate(knex, ...)的成果对外层父级实例同样可见。否则按默认封装规则外层路由将访问不到fastify.knex。if (!fastify.knex)做幂等保护防止插件被重复注册时重复创建连接。knex(options)使用 register 传入的options作为连接配置含 client、connection 等 Knex 标准配置文档只强调模式实际使用时请在options中传入 Knex 所需全部配置。fastify.addHook(onClose, ...)保证关闭顺序销毁 Knex 连接池knex.destroy接受回调与done衔接避免进程悬挂。export default fp(...)同时示范了 ES Module 导出写法{ name: fastify-knex-example }是插件元数据便于 Fastify 在插件依赖校验与日志中识别它对应 lib/plugin-utils.js 中的registerPluginName逻辑。注意事项若把该插件包发布为 CommonJS 包应写作module.exports fp(...)。仓库 package.json 使用type: commonjs主入口fastify.js为 CJSESM 用户请参考文档与仓库 examples/simple.mjs 等示例自行适配。为数据库引擎从零编写插件如果某个数据库引擎没有任何现成插件可以用同一套骨架自写。下面是为 MySQL 从零编写的基础插件官方文档特别强调这是精简教学示例生产环境请使用官方fastify/mysql插件const fp require(fastify-plugin) const mysql require(mysql2/promise) function fastifyMysql(fastify, options, done) { const connection mysql.createConnection(options) if (!fastify.mysql) { fastify.decorate(mysql, connection) } fastify.addHook(onClose, (fastify, done) connection.end().then(done).catch(done)) done() } export default fp(fastifyMysql, { name: fastify-mysql-example })该模板与“Knex 库插件”模式一一对应可归纳为一条通用写作套路适用于任何数据库步骤说明1. 用options建立连接mysql.createConnection(options)配置由使用方通过register(plugin, options)注入2. 防重复装饰if (!fastify.mysql) { fastify.decorate(mysql, connection) }3. 关闭时释放addHook(onClose, ...)mysql2 的connection.end()返回 Promise用.then(done).catch(done)桥接 Fastify 的回调式done4. 跳过封装用fp(...)包装导出保证装饰器全局可见5. 标注名称{ name: fastify-mysql-example }便于元信息管理如果改为连接池如mysql.createPool或替换为 redis、sqlite 等驱动仅需改动第 1、3 步的连接创建与销毁调用整体骨架完全复用。这样无论生态中缺哪种数据库团队都能用一致的模式补齐也方便后续将成熟插件回馈社区。用 Postgrator 做数据库迁移数据库 schema 迁移是数据库管理与开发中不可或缺的一环它提供可重复、可测试的 schema 变更方式防止手工改表造成的数据丢失。Fastify 不干预迁移环节——与“数据库无关”理念一致任何 Node.js 迁移工具都可直接使用。官方指南重点介绍 Postgrator它支持 Postgres、MySQL、SQL Server 与 SQLite。若使用 MongoDB官方建议参考 migrate-mongo 工具。迁移文件命名规范Postgrator 通过目录中的一组 SQL 脚本描述 schema 变更migrations目录下每个文件需遵循如下命名模式[version].[action].[optional-description].sql三个字段含义如下version必须是递增的数字例如001或时间戳形式。action只能是do或undo。do执行该版本undo回滚它——可类比其他迁移工具中的up/down。optional-description描述该迁移做了哪些改动。虽然可选但官方强烈建议每次都写让团队从文件名即可看出变更内容。一个建表迁移的完整示例准备一个建users表的迁移并运行npm i pg postgrator安装所需依赖示例面向 Postgres。迁移文件001.do.create-users-table.sqlCREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY NOT NULL, created_at DATE NOT NULL DEFAULT CURRENT_DATE, firstName TEXT NOT NULL, lastName TEXT NOT NULL );驱动迁移的 Node 脚本const pg require(pg) const Postgrator require(postgrator) const path require(node:path) async function migrate() { const client new pg.Client({ host: localhost, port: 5432, database: example, user: example, password: example, }); try { await client.connect(); const postgrator new Postgrator({ migrationPattern: path.join(__dirname, /migrations/*), driver: pg, database: example, schemaTable: migrations, currentSchema: public, // Postgres and MS SQL Server only execQuery: (query) client.query(query), }); const result await postgrator.migrate() if (result.length 0) { console.log( No migrations run for schema public. Already at the latest one. ) } console.log(Migration done.) process.exitCode 0 } catch(err) { console.error(err) process.exitCode 1 } await client.end() } migrate()对关键配置的补充解读migrationPattern指向存放迁移 SQL 的目录glob 模式。driver迁移目标数据库驱动此处为pg对应 Postgres。schemaTablePostgrator 记录已执行版本的元数据表名这里为migrations。currentSchema当前 schemapublic仅 Postgres 与 MS SQL Server 需要其余驱动会忽略。execQuery把执行交给外部管理的 pg 连接client.query便于复用同一个连接并纳入统一的事务/错误处理。postgrator.migrate()默认向最新版本迁移返回的result为空数组表示“已是最新无需迁移”脚本据此打印提示。脚本通过process.exitCode显式表达成功0与失败1失败信息完整打印到 stderr方便接入 CI。实际项目里可以把这段migrate()脚本挂进package.json的 scripts例如migrate: node migrate.js在部署前、发布流程中或 CI 阶段调用而由于 Fastify 的数据库连接同样在插件加载期间就绪迁移逻辑与 Web 服务可完全解耦——这正是“数据库无关”带来的工程灵活性。小结围绕 Fastify 的数据库接入可以提炼出三条主线开箱即用MySQL、Postgres、Redis、MongoDB 都有 Fastify 官方维护的fastify/*插件注册即得全局连接对象代码风格高度统一且有closeClient/forceClose等生命周期选项精确控制连接释放。按需自建没有现成插件时只需套用fp(pluginFn, { name }) fastify.decorate onClose 清理这一通用骨架即可为任意数据库引擎或 ORM/Query BuilderKnex、Prisma、TypeORM……写出规范的 Fastify 插件。理解背后的封装模型与装饰器机制是掌握这条主线的钥匙。迁移自成体系schema 变更交给 Postgrator 等专用工具按version.action.description.sql组织迁移文件、以 Node 脚本驱动执行与 Fastify 应用进程解耦。进一步阅读建议插件写作的完整方法论见 docs/Guides/Plugins-Guide.md 与 docs/Guides/Write-Plugin.mdregister/decorate/hooks 的 API 细节可分别查阅 docs/Reference/Plugins.md、docs/Reference/Decorators.md、docs/Reference/Hooks.mdFastify 生命周期各事件触发顺序见 docs/Reference/Lifecycle.md。相关源码入口为 fastify.js、lib/decorate.js、lib/plugin-utils.js仓库内完整示例可参考 examples/use-plugin.js 与 examples/plugin.js。【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考