news 2026/9/6 21:54:19

Fastify 入门指南:从第一个服务器到插件、数据校验与序列化的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fastify 入门指南:从第一个服务器到插件、数据校验与序列化的完整实战

Fastify 入门指南:从第一个服务器到插件、数据校验与序列化的完整实战

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

本文基于 Fastify 官方入门文档(docs/Guides/Getting-Started.md)并结合仓库源码编写,带你完成一次完整的上手流程:安装框架、搭建第一个 HTTP 服务器、理解register插件机制与异步启动(bootstrapping)、掌握基于 JSON Schema 的请求校验与响应序列化,并了解内容类型解析、测试注入与 fastify-cli 的使用方式。读完之后,你将能独立搭建一个多文件、可校验、可扩展的 Fastify 应用,并理解其关键 API 在 fastify.js 与 lib/server.js 中的真实实现。

一、安装

Fastify 是一个基于 Node.js 的高性能、低开销 Web 框架(仓库描述为 "Fast and low overhead web framework, for Node.js",见 package.json)。安装方式与大多数 npm 包一致:

# npm npm i fastify # yarn yarn add fastify

注意当前仓库的版本号:package.json中标注为6.0.0-alpha.2(fastify.js 中VERSION = '6.0.0-alpha.2'),其核心依赖包括avvio(插件加载器)、find-my-way(路由器)、pino(日志)、fast-json-stringify(响应序列化)与light-my-request(请求注入测试)等,这些依赖决定了后文每个特性的底层实现归属。

二、第一个服务器

创建一个最简 Fastify 服务器需要三步:实例化、声明路由、监听端口。官方文档给出的完整示例(同时覆盖 ESM 与 CommonJS 两种写法)如下:

// 引入框架并实例化 // ESM import Fastify from 'fastify' const fastify = Fastify({ logger: true }) // CommonJS const fastify = require('fastify')({ logger: true }) // 声明路由 fastify.get('/', function (request, reply) { reply.send({ hello: 'world' }) }) // 启动服务器! fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } // 服务器已监听在 ${address} })

仓库内的 examples/simple.js 就是一个可运行的最简版本,它在官方示例基础上给 GET/附加了response序列化 schema(后文第五节会讲到),而 examples/asyncawait.js 则演示了async/await风格。

async/await 风格

Fastify 原生支持async/await,handler 可以直接return一个对象(或 Promise),框架会替你调用序列化与发送:

// ESM import Fastify from 'fastify' const fastify = Fastify({ logger: true }) // CommonJS const fastify = require('fastify')({ logger: true }) fastify.get('/', async (request, reply) => { return { hello: 'world' } }) /** * 启动服务器! */ const start = async () => { try { await fastify.listen({ port: 3000 }) } catch (err) { fastify.log.error(err) process.exit(1) } } start()

如果项目使用 ECMAScript Modules(ESM),记得在package.json中加上"type": "module"。两种风格的差异本质上是回调式与 Promise 式的错误处理:回调式通过listenerr参数捕获启动错误;Promise 式则用try/catch包裹await fastify.listen(...)

监听地址:localhost、0.0.0.0 与 IPv6

官方文档特别提醒:示例默认只监听 localhost 的127.0.0.1接口。这一行为不是文档的口头约定,而是源码里的真实默认值。lib/server.js 中listen的签名是:

function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined)

并且当host未显式提供时会回退到'localhost'(lib/server.js)。因此:

  • 想监听所有 IPv4 接口,应显式指定0.0.0.0
fastify.listen({ port: 3000, host: '0.0.0.0' }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } fastify.log.info(`server listening on ${address}`) })
  • 指定::1表示仅接受本机 IPv6 连接;指定::则接受所有 IPv6 地址的连接,并在操作系统支持时同时接受 IPv4 连接。
  • 部署到 Docker(或同类容器)时,用0.0.0.0::暴露应用是最简单的方式。
  • 使用0.0.0.0时,回调中拿到的address是通配符所指代的第一个地址。这一点在源码中可以看到对应实现:lib/server.js 的getAddresses会把0.0.0.0展开为本机所有 IPv4 网卡地址(内网接口排在前面),并逐条记录Server listening at <address>日志,最终返回的第一个地址即为回调参数。

另外,源码注释中说明了对localhost的额外处理:参考 Node.js 的dns.lookup结果同时绑定127.0.0.1::1(见multipleBindings函数,lib/server.js),这与文档"默认监听 localhost"的描述是一致的。

三、第一个插件:register 与异步启动

"在 JavaScript 中万物皆对象,在 Fastify 中万物皆插件"。这是理解 Fastify 架构的钥匙。先看一个把路由移到外部文件的例子(路由声明的完整规则可参考 Routes 参考文档):

入口文件:

// ESM import Fastify from 'fastify' import routes from './our-first-routes.js' /** * @type {import('fastify').FastifyInstance} */ const fastify = Fastify({ logger: true }) fastify.register(routes) fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } })
// CommonJS const fastify = require('fastify')({ logger: true }) fastify.register(require('./our-first-routes')) fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } })

路由文件our-first-routes.js

/** * 封装路由的插件 * @param {FastifyInstance} fastify 被封装的 Fastify 实例 * @param {Object} options 插件选项 */ async function routes (fastify, options) { fastify.get('/', async (request, reply) => { return { hello: 'world' } }) } // ESM export default routes // CommonJS module.exports = routes

这个例子里用的是registerAPI——它是 Fastify 框架的核心,也是添加路由、插件的唯一途径。这一点在源码中可以直接印证:fastify.js 中,registerafterreadyonCloseclose这些方法初始时都是null,随后由Avvio插件加载器注入:

const avvio = Avvio(fastify, { autostart: false, timeout: isNaN(avvioPluginTimeout) === false ? avvioPluginTimeout : defaultInitOptions.pluginTimeout, expose: { use: 'register' } }) // 覆写 override 以支持插件的封装(encapsulation) avvio.override = override

avvio.override = override(实现位于 lib/plugin-override.js)正是 Fastify作用域封装的入口:每次register都会基于override产生一个子实例,子实例的路由、装饰器、hooks 被限制在自己的作用域内——这也是官方推荐"先注册生态插件、再注册自定义插件"顺序的原因。

数据库连接的异步启动

真实应用中,很多功能(比如数据库连接)必须在服务器开始接受连接之前就绪。典型做法是用复杂回调或手工 Promise 把框架 API 和业务代码搅在一起;而 Fastify 依靠register的串行加载机制把它内化了:插件按声明顺序逐个加载,且当前插件加载完成后才加载下一个

@fastify/mongodb为例。先安装依赖:

npm i fastify-plugin @fastify/mongodb

server.js

// ESM import Fastify from 'fastify' import dbConnector from './our-db-connector.js' import routes from './our-first-routes.js' const fastify = Fastify({ logger: true }) fastify.register(dbConnector) fastify.register(routes) fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } })

CommonJS 版本把import换成require即可(fastify.register(require('./our-db-connector'))等)。

our-db-connector.js

// ESM import fastifyPlugin from 'fastify-plugin' import fastifyMongo from '@fastify/mongodb' /** * @param {FastifyInstance} fastify * @param {Object} options */ async function dbConnector (fastify, options) { fastify.register(fastifyMongo, { url: 'mongodb://localhost:27017/test_database' }) } // 用 fastify-plugin 包装插件函数, // 可把插件内部声明的装饰器(decorators)和 hooks 暴露给父作用域 export default fastifyPlugin(dbConnector)
// CommonJS const fastifyPlugin = require('fastify-plugin') async function dbConnector (fastify, options) { fastify.register(require('@fastify/mongodb'), { url: 'mongodb://localhost:27017/test_database' }) } module.exports = fastifyPlugin(dbConnector)

our-first-routes.js(路由通过fastify.mongo装饰器访问数据库):

/** * 提供封装路由的插件 * @param {FastifyInstance} fastify 被封装的 fastify 实例 * @param {Object} options 插件选项 */ async function routes (fastify, options) { const collection = fastify.mongo.db.collection('test_collection') fastify.get('/', async (request, reply) => { return { hello: 'world' } }) fastify.get('/animals', async (request, reply) => { const result = await collection.find().toArray() if (result.length === 0) { throw new Error('No documents found') } return result }) fastify.get('/animals/:animal', async (request, reply) => { const result = await collection.findOne({ animal: request.params.animal }) if (!result) { throw new Error('Invalid value') } return result }) const animalBodyJsonSchema = { type: 'object', required: ['animal'], properties: { animal: { type: 'string' } } } const schema = { body: animalBodyJsonSchema } fastify.post('/animals', { schema }, async (request, reply) => { // 用 `request.body` 获取客户端发送的数据 const result = await collection.insertOne({ animal: request.body.animal }) return result }) } module.exports = routes

这个例子里引入了几个新概念,值得回顾:

  1. register同时用于数据库连接器和路由。Fastify 按声明顺序串行加载插件:先加载dbConnector,加载完成(数据库连接就绪)后才加载routes,因此routes里能安全地使用fastify.mongo
  2. 插件加载的触发时机:当你调用fastify.listen()fastify.inject()fastify.ready()时开始。这一点在源码中有清晰对应:fastify.js 中的ready函数负责执行onReadyhooks 并驱动 Avvio 启动流程;而inject在服务器尚未就绪时会先调用this.ready(...)(fastify.js),再执行注入请求——这就是为什么测试中可以用inject触发完整的插件加载。
  3. decorateAPI:MongoDB 插件通过decoratemongo对象挂到 Fastify 实例上供整个作用域使用。官方鼓励这种用法以提高代码复用、减少逻辑重复。装饰器的实现位于 lib/decorate.js,公开 APIdecoratedecorateReplydecorateRequesthasDecorator等挂载在 fastify.js 的实例上。
  4. fastify-plugin包装器:插件默认被封装在自己的作用域内,其内部声明的装饰器对外不可见;用fastifyPlugin(dbConnector)包装后,装饰器和 hooks 会被提升到父作用域(作用域细节可参考 Plugins 参考文档的 Handle the scope 一节)。

想更深入地了解插件机制、如何开发新插件以及完整的异步启动方案,请阅读 插件指南(Plugins-Guide)。

推荐的插件加载顺序

为保证应用行为一致、可预测,官方强烈推荐按以下顺序加载代码:

└── plugins(来自 Fastify 生态的插件) └── your plugins(你的自定义插件) └── decorators(装饰器) └── hooks(钩子) └── your services(你的业务服务)

这样你在当前作用域内始终可以访问已声明的全部属性。

同时,Fastify 提供了坚固的封装模型,帮助你把应用构建成相互独立的服务。如果只想给一部分路由注册某个插件,只需在对应的服务内部复制上述结构即可:

└── plugins(来自 Fastify 生态的插件) └── your plugins(你的自定义插件) └── decorators(装饰器) └── hooks(钩子) └── your services(你的业务服务) │ └── service A │ └── plugins(来自 Fastify 生态的插件) │ └── your plugins(你的自定义插件) │ └── decorators(装饰器) │ └── hooks(钩子) │ └── your services(你的业务服务) │ └── service B └── plugins(来自 Fastify 生态的插件) └── your plugins(你的自定义插件) └── decorators(装饰器) └── hooks(钩子) └── your services(你的业务服务)

四、校验你的数据

数据校验是框架的核心概念。Fastify 使用JSON Schema校验入站请求。给路由传一个 options 对象,其中schema键包含该路由的全部 schema,包括bodyquerystringparamsheaders

/** * @type {import('fastify').RouteShorthandOptions} * @const */ const opts = { schema: { body: { type: 'object', properties: { someKey: { type: 'string' }, someOtherKey: { type: 'number' } } } } } fastify.post('/', opts, async (request, reply) => { return { hello: 'world' } })

从源码结构看,schema 的处理统一收口在 lib/schema-controller.js:实例化时通过SchemaController.buildSchemaController(null, options.schemaController)构建(fastify.js),校验器编译器默认由@fastify/ajv-compiler提供(见 package.json 依赖),也允许用setValidatorCompiler替换为自定义实现。完整的校验与序列化规则参见 Validation and Serialization 参考文档。

五、序列化你的数据

Fastify 对 JSON 有一等公民级的支持,JSON 请求体解析与 JSON 响应输出都做了深度优化。要加速 JSON 序列化,使用 schema 选项的response键:

/** * @type {import('fastify').RouteShorthandOptions} * @const */ const opts = { schema: { response: { 200: { type: 'object', properties: { hello: { type: 'string' } } } } } } fastify.get('/', opts, async (request, reply) => { return { hello: 'world' } })

指定这样的 schema 后,序列化速度可以提升到 2~3 倍。更重要的是,它还起到防泄漏作用:Fastify 只会序列化响应 schema 中声明的数据,schema 之外的敏感字段不会意外下发。底层实现上,响应 schema 会编译成fast-json-stringify的专用序列化函数(依赖见 package.json),而 examples/simple.js 与 examples/asyncawait.js 中使用的正是这套response: { 200: ... }写法,可以直接作为运行参考。更多细节见 Validation and Serialization 参考文档。

六、解析请求负载

Fastify 原生解析application/jsontext/plain两种请求负载,解析结果挂在 Request 对象的request.body上。下面的例子把解析后的 body 原样返回给客户端:

/** * @type {import('fastify').RouteShorthandOptions} */ const opts = {} fastify.post('/', opts, async (request, reply) => { return request.body })

源码印证:lib/content-type-parser.js 在初始化ContentTypeParser时默认注册了两个解析器,并把它们列入解析器白名单:

this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse])) this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) this.parserList = ['application/json', 'text/plain']

JSON 解析基于secure-json-parse(防原型污染,onProtoPoisoning/onConstructorPoisoning选项可在实例化时定制,见 fastify.js)。要支持其他 Content-Type,请阅读 Content-Type Parser 参考文档,它同时介绍了默认解析能力和自定义解析器的注册方式。

七、扩展你的服务器与测试

Fastify 的设计哲学是"极简内核 + 生态扩展":它有意不做 "batteries included" 的框架,而是依赖一个庞大的 生态系统 来补齐文件上传、认证、静态资源等功能。

在测试方面,Fastify 不内置测试框架,但官方推荐使用基于框架自身特性的测试方式——核心就是fastify.inject():它基于light-my-request在进程内模拟完整的 HTTP 请求/响应链路,无需真正监听端口。从源码看,inject会按需加载light-my-request并调用路由处理器httpHandler,且在服务器未就绪时自动等待插件加载完成(fastify.js)。完整的测试最佳实践见 Testing 指南。

八、用 CLI 运行服务器

Fastify 生态中的 fastify-cli 提供了脚手架与项目管理能力(它是一个独立包,需单独安装):

npm i fastify-cli

也可以用-g全局安装。然后在package.json中加入:

{ "scripts": { "start": "fastify start server.js" } }

并创建你的服务器文件。注意 fastify-cli 的约定:服务器文件导出的是一个插件函数(与 examples/plugin.js 展示的经典插件形式一致),由 CLI 负责实例化和启动:

// server.js 'use strict' module.exports = async function (fastify, opts) { fastify.get('/', async (request, reply) => { return { hello: 'world' } }) }

最后执行:

npm start

九、小结:入门后的学习路径

回顾本文覆盖的 Fastify 入门主线:

能力核心 API深入文档 / 源码
安装与实例化Fastify({ logger: true })fastify.js、lib/server.js
路由声明fastify.get/post/...fastify.routeRoutes 参考
监听与地址绑定fastify.listen({ port, host })lib/server.js
插件与异步启动registerfastify-pluginPlugins 参考、Plugins-Guide
请求校验schema.body/params/querystring/headersValidation-and-Serialization、lib/schema-controller.js
响应序列化schema.responseexamples/simple.js、lib/reply.js
请求体解析request.body(JSON / 纯文本)ContentTypeParser 参考、lib/content-type-parser.js
测试注入fastify.injectTesting 指南
生态扩展生态插件Ecosystem 指南

掌握以上内容后,你可以沿着 Plugins-Guide 深入插件与作用域,沿 Validation and Serialization 构建严格的接口契约,并用 Testing 指南 的inject方式为每个路由建立可靠的测试。这些章节与本文一脉相承,共同构成 Fastify 从入门到生产的完整路径。

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

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 21:54:15

高级班文本拆解:从知识管理到内容创作的方法论落地

简介&#xff1a;这份《I_show高级班文本》是一份英语口语高级课程对话练习素材&#xff0c;面向具备一定英语基础、希望提升真实交际能力的学习者。文档以多个场景化对话片段为主体&#xff0c;涵盖日常邀约、人物描述、文化差异、共同兴趣、情感变化、观点争论、社交活动与自…

作者头像 李华
网站建设 2026/9/6 21:51:59

CDGA模拟真题全解析:考点分布与高效备考刷题策略

简介&#xff1a;《CDGA模拟真题100道&#xff08;含历年真题&#xff09;》是面向DAMA数据治理工程师认证考生的PDF版刷题资料。整份资料为单个PDF文件&#xff0c;大小222KB&#xff0c;内容围绕数据治理的基础概念、最佳实践和法规要求展开&#xff0c;覆盖人员与过程、数据…

作者头像 李华
网站建设 2026/9/6 21:51:29

免费一键 4K:本地视频超分工具 Video2X

免费一键 4K&#xff1a;本地视频超分工具 Video2X 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/video2x 当你把…

作者头像 李华
网站建设 2026/9/6 21:50:49

UL 840-2016绝缘配合标准详解:电气间隙与爬电距离设计要点

简介&#xff1a;UL 840-2016由美国保险商实验室发布&#xff0c;是针对电气设备绝缘协调的权威安全标准&#xff0c;业内广泛应用&#xff0c;适用对象涵盖产品设计工程师、安规测试人员及质量管理人士&#xff0c;核心解决绝缘系统中间隙与爬电距离如何确定和管控的问题。标准…

作者头像 李华
网站建设 2026/9/6 21:39:06

教育学硕士亲测:智能排版10分钟搞定论文格式的完整流程

读教育学硕士的第三年&#xff0c;我帮导师整理过十几份学生论文&#xff0c;最深的体会是&#xff1a;内容再好&#xff0c;格式乱了就先输一半。标题字号不统一、图表编号对不上、参考文献一会儿GB/T 7714一会儿自创格式&#xff0c;页眉页码更是重灾区。教育学院的格式细则足…

作者头像 李华