Fastify 数据库接入实战指南:官方数据库插件、自定义连接插件与 SQL 迁移
2026/9/9 23:04:12 网站建设 项目流程

Fastify 数据库接入实战指南:官方数据库插件、自定义连接插件与 SQL 迁移

【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify

Fastify 将 Web 框架与数据库彻底解耦(database agnostic),任何 Node.js 数据库驱动都能通过插件化方式接入框架。本文以 docs/Guides/Database.md 为主线,系统讲解 Fastify 官方维护的 MySQL、Postgres、Redis、MongoDB 连接插件用法,并深入 Fastify 源码揭示registerdecorateonClose与封装(encapsulation)机制,最终教会你两类核心实战能力:为数据库驱动或数据库库(ORM/Query Builder)编写可复用的 Fastify 插件,以及用 Postgrator 把数据库迁移融入 Fastify 应用的开发与发布流程。

前置认知:Fastify 的数据库哲学

Fastify 本身不绑定任何数据库,它的核心定位是“Fast and low overhead web framework”。在 Fastify 生态中,数据库能力全部由插件提供,官方在 Fastify 组织内维护了若干连接插件,覆盖主流关系型与非关系型引擎。

这一点对本指南至关重要:如果你的目标数据库暂时没有官方或社区插件,也完全不影响使用。因为 Fastify 只是提供一个插件封装框架,你可以参考本指南中几个官方插件的写法,为自己选用的数据库引擎编写一个等同的插件(本文后续“编写数据库引擎插件”一节会手把手演示)。

另外,如果你打算自己动手写插件,请先通读官方插件编写指南:docs/Guides/Plugins-Guide.md,其中详细介绍了registerdecorate、hooks 与封装模型。下面的示例都建立在fastify.register(...)与装饰器之上,建议配合阅读。

官方维护的四大数据库连接插件

以下四个插件的连接对象在被register加载后,会以装饰器的形式挂到 Fastify 实例上,因此路由处理器内可以直接通过fastify.mysqlfastify.pgfastify.redisfastify.mongo访问连接池/客户端。

MySQL:@fastify/mysql

安装插件:

npm i @fastify/mysql

注册并查询的基本用法:

const fastify = require('fastify')() fastify.register(require('@fastify/mysql'), { connectionString: 'mysql://root@localhost/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 空密码连接串,且示例密码仅作演示。

Postgres:@fastify/postgres

Postgres 需要同时安装pg(node-postgres 驱动)与 Fastify 封装插件:

npm i pg @fastify/postgres

用法:

const fastify = require('fastify')() fastify.register(require('@fastify/postgres'), { connectionString: 'postgres://postgres@localhost/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()获取单条连接以支撑事务。

Redis:@fastify/redis

安装:

npm i @fastify/redis

Redis 插件既支持独立 host 参数,也支持完整的url(redis:// 协议):

'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.redisredis.get/redis.set均为回调风格,其中redis.set(key, value)一旦成功,reply.send返回{ status: 'ok' }作为业务结果。

连接生命周期注意事项:默认情况下@fastify/redis不会在 Fastify 服务关闭时关闭客户端连接。如果你希望服务close时主动释放 Redis 连接(避免进程无法正常退出),需要显式传入已创建的client并开启closeClient: true

fastify.register(require('@fastify/redis'), { client: redis, closeClient: true })

这与下文讨论的“数据库插件普遍需要在onClosehook 中销毁连接”是同一思想。

MongoDB:@fastify/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.pgfastify.redisfastify.mongo等),此后所有路由即可通过fastify.mysql访问同一连接。

decorate还可用于fastify.decorateRequestfastify.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' })

逐段拆解:

  1. fp(knexPlugin, ...)是关键fpfastify-plugin模块,它通过给函数打上特殊标记(源码 lib/plugin-utils.js 中的Symbol.for('skip-override')),告知 Fastify“跳过封装”,使fastify.decorate('knex', ...)的成果对外层父级实例同样可见。否则按默认封装规则,外层路由将访问不到fastify.knex
  2. if (!fastify.knex)做幂等保护,防止插件被重复注册时重复创建连接。
  3. knex(options)使用 register 传入的options作为连接配置(含 client、connection 等 Knex 标准配置),文档只强调模式,实际使用时请在options中传入 Knex 所需全部配置。
  4. fastify.addHook('onClose', ...)保证关闭顺序:销毁 Knex 连接池(knex.destroy接受回调,与done衔接),避免进程悬挂。
  5. 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为 CJS;ESM 用户请参考文档与仓库 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 的回调式done
4. 跳过封装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:只能是doundodo执行该版本,undo回滚它——可类比其他迁移工具中的up/down
  • optional-description:描述该迁移做了哪些改动。虽然可选,但官方强烈建议每次都写,让团队从文件名即可看出变更内容。

一个建表迁移的完整示例

准备一个建users表的迁移,并运行npm i pg postgrator安装所需依赖(示例面向 Postgres)。

迁移文件001.do.create-users-table.sql

CREATE 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)。
  • schemaTable:Postgrator 记录已执行版本的元数据表名,这里为migrations
  • currentSchema:当前 schema(public),仅 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 的数据库接入,可以提炼出三条主线:

  1. 开箱即用:MySQL、Postgres、Redis、MongoDB 都有 Fastify 官方维护的@fastify/*插件,注册即得全局连接对象,代码风格高度统一,且有closeClient/forceClose等生命周期选项精确控制连接释放。
  2. 按需自建:没有现成插件时,只需套用fp(pluginFn, { name }) + fastify.decorate + onClose 清理这一通用骨架,即可为任意数据库引擎或 ORM/Query Builder(Knex、Prisma、TypeORM……)写出规范的 Fastify 插件。理解背后的封装模型与装饰器机制是掌握这条主线的钥匙。
  3. 迁移自成体系:schema 变更交给 Postgrator 等专用工具,按version.action.description.sql组织迁移文件、以 Node 脚本驱动执行,与 Fastify 应用进程解耦。

进一步阅读建议:插件写作的完整方法论见 docs/Guides/Plugins-Guide.md 与 docs/Guides/Write-Plugin.md;register/decorate/hooks 的 API 细节可分别查阅 docs/Reference/Plugins.md、docs/Reference/Decorators.md、docs/Reference/Hooks.md;Fastify 生命周期各事件触发顺序见 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询