# fastify * 官网: https://fastify.dev * 中文文档: https://www.fastify.cn/docs/latest/Getting-Started/ * [提升 Node.js 服务端性能:Fastify 框架](https://juejin.cn/post/7340109700767154228) * [Fastify+TS实现基础IM服务](https://juejin.cn/post/7356535808931053606) ## 快速开始 ```sh pnpm add fastify ``` ```js // Import the framework and instantiate it import Fastify from "fastify"; const fastify = Fastify({ logger: true, }); // Declare a route fastify.get("/", async function handler(request, reply) { return { hello: "world" }; }); // Run the server! try { await fastify.listen({ port: 3000 }); } catch (err) { fastify.log.error(err); process.exit(1); } ``` 因为使用esm模块, `package.json` 需要设置`"type": "module"` ## 使用cli快速生成项目 ```sh npm install --global fastify-cli fastify generate myproject ``` ## 插件、装饰器、中间件、钩子 ### 插件 > 用于添加功能、共享代码或封装逻辑 ===在 Fastify 中,一切都是插件=== * 一个插件可以是一个函数,该函数接受 Fastify 实例、选项和回调函数作为参数 * 插件可以注册路由、添加装饰器、声明新的钩子,甚至可以封装其他插件,用于构建模块化的应用 * 在应用启动时加载,按照注册的顺序执行 * `fastify.register()` 例子: ```js async function routes (fastify, options) { fastify.get('/', async (request, reply) => { return { hello: 'world' } }) } export default routes ``` ```js import Fastify from 'fastify' import routes from './routers/root.js' const fastify = Fastify({ logger: true }) // 使用register注册 fastify.register(routes) try { await fastify.listen({ port: 3000 }) } catch (err) { fastify.log.error(err) process.exit(1) } ``` ### 装饰器 > 扩展 Fastify 实例、请求(Request)和回复(Reply)对象,通过添加新的方法或属性 * 例如,可以添加一个装饰器来添加一个方法,该方法在每个请求中都可用,用于访问共享的配置数据或服务 ### 中间件 > 支持使用 Express/Connect 风格的中间件,主要用于兼容性或特定功能的集成 * 应谨慎使用,因为不当使用可能会绕过 Fastify 的一些优化,影响性能 ### 钩子 > 允许开发者在请求生命周期的不同阶段介入执行逻辑 * 例如,在请求接收之后、路由解析之前、发送响应之前等 * 可以用于执行一些预处理或后处理逻辑,如权限检查、请求日志记录、修改响应等 * `fastify.addHook` 常见的钩子: * `onRequest`:在请求被接收后立即执行,但在任何其他处理之前。 * `preParsing`:在请求体解析之前执行。 * `preValidation`:在路由级别的验证之前执行。 * `preHandler`:在路由处理函数之前执行。 * `preSerialization`:在响应被序列化之前执行,发送给客户端之前。 * `onSend`:在响应发送给客户端之前,但在序列化之后执行。 * `onResponse`:在响应完全发送给客户端后执行。 钩子的使用 ```js fastify.get( '/example', { preHandler: (request, reply, done) => { // 在路由处理器之前执行某些代码 done() }, }, (request, reply) => { reply.send({ hello: 'world' }) } ) ``` #### 生命周期 ``` Incoming Request │ └─▶ Routing │ └─▶ Instance Logger │ 4**/5** ◀─┴─▶ onRequest Hook 在请求完全被接收之后,任何解析之前触发.用来执行一些认证或者其他检查 │ 4**/5** ◀─┴─▶ preParsing Hook 解析请求之前触发,但在它被路由处理之后。用来修改原始请求对象,例如解密数据。 │ 4**/5** ◀─┴─▶ Parsing │ 4**/5** ◀─┴─▶ preValidation Hook 在执行路由级别的验证之前触发.用来在标准验证之前预处理一些参数 │ 400 ◀─┴─▶ Validation │ 4**/5** ◀─┴─▶ preHandler Hook 在执行路由的处理函数之前触发。这是处理请求前最后的步骤,可以做一些预处理工作,如权限校验。 │ 4**/5** ◀─┴─▶ User Handler │ └─▶ Reply │ 4**/5** ◀─┴─▶ preSerialization Hook 在发送响应之前,序列化之前触发。可以用来修改响应数据,或者在序列化复杂类型之前进行转换。 │ └─▶ onSend Hook 可以修改响应体或者添加响应头 │ 4**/5** ◀─┴─▶ Outgoing Response │ └─▶ onResponse Hook 主要用于记录日志或者执行清理任务 ``` #### 响应的数据流 ```js ★ schema validation Error │ └─▶ schemaErrorFormatter │ reply sent ◀── JSON ─┴─ Error instance │ │ ★ throw an Error ★ send or return │ │ │ │ │ │ ▼ │ reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler ◀─────┘ │ reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook │ └─▶ reply sent ``` #### 添加钩子 ```js fastify.addHook('preHandler', (request, reply, done) => { // some code done() }) ``` ## JSON Schema (验证数据) > 验证客户端请求的数据和格式化响应数据 * 验证路由参数、查询字符串、请求体和响应体 在 schema 的选项中设置 `response` 的值,能够加快 JSON 的序列化, 因为 Fastify 仅对 schema 里出现的数据进行序列化。 ```js const opts = { schema: { response: { 200: { type: 'object', properties: { hello: { type: 'string' } } } } } } fastify.get('/', opts, async (request, reply) => { return { hello: 'world' } }) ``` 还可以验证 `body`、`querystring`、`params` 以及 `header`。 ## 路由 > 实际项目中, 路由分散在不同的文件和模块中, 使用插件注册 ### 自定义路由处理器 ```js const customHandler = (request, reply) => { // 自定义处理逻辑 }; fastify.route({ method: 'GET', url: '/custom', handler: customHandler }); ``` ### 内置的快速方法 ```ts fastify.get(path, [options], handler) fastify.head(path, [options], handler) fastify.post(path, [options], handler) fastify.put(path, [options], handler) fastify.delete(path, [options], handler) fastify.options(path, [options], handler) fastify.patch(path, [options], handler) // 你还可以链式调用它们 fastify.get('/path', handler).post('/path', handler); ``` ### 静态路由 ```js // 参数 fastify.get('/example/:userId', function (request, reply) { // curl ${app-url}/example/12345 // userId === '12345' const { userId } = request.params; // your code here }) fastify.get('/example/:userId/:secretToken', function (request, reply) { // curl ${app-url}/example/12345/abc.zHi // userId === '12345' // secretToken === 'abc.zHi' const { userId, secretToken } = request.params; // your code here }) // 通配符 fastify.get('/example/*', function (request, reply) {}) ``` ### 动态路由参数 ```js fastify.get('/user/:id', (request, reply) => { const userId = request.params.id; // 根据 userId 获取用户信息... }); ``` ### 路由前缀 ```js fastify.register(require('./user-routes'), { prefix: '/api/v1/users' }); ``` ### 404处理 ```js fastify.setNotFoundHandler((request, reply) => { reply.status(404).send({ error: 'Not Found' }); }); ``` ### 路由级别的插件 为特定的路由添加特定的功能,比如认证、日志记录等 ```js fastify.get('/private', { preHandler: fastify.auth }, (request, reply) => { // 只有通过认证的请求才能到达这里 }); ``` ### websocket ```js fastify.get('/ws', { websocket: true }, (connection /* SocketStream */, req /* FastifyRequest */) => { connection.socket.on('message', message => { // 处理 WebSocket 消息 }); }); ``` ### 高级查询解析 自定义查询字符串的解析器 ```js const qs = require('qs'); fastify.setQuerystringParser((str) => { return qs.parse(str); }); ``` ## 请求和响应对象 ### 请求对象 * `request.raw`: 原生的 Node.js HTTP 请求对象。 * `request.body`: 请求体的内容,Fastify 会根据你设置的内容类型自动解析它。 * `request.query`: 解析后的查询字符串参数。 * `request.params`: 路由参数值,例如 `/user/:id` 中的 `id`。 * `request.headers`: 请求头对象。 * `request.id`: 每个请求的唯一标识符,可以用于日志记录或跟踪。 * `request.log`: 日志记录器实例,可以用来记录请求相关的日志。 * `request.ip`: 客户端的 IP 地址。 * `request.method`: HTTP 请求方法(例如,`GET`, `POST` 等)。 * `request.url`: 请求的 URL 字符串。 ### 响应对象 * `reply.raw`: 原生的 Node.js HTTP 响应对象。 * `reply.code(statusCode)`: 设置响应的状态码。 * `reply.header(name, value)`: 设置响应头。 * `reply.headers(headers)`: 一次性设置多个响应头。 * `reply.type(contentType)`: 设置 `Content-Type` 响应头。 * `reply.redirect(url)`: 重定向到指定的 URL。 * `reply.send(payload)`: 发送响应数据。payload 可以是一个字符串、Buffer、对象或者 Stream。 * `reply.serializer(customSerializer)`: 设置自定义的序列化函数来转换响应数据。 * `reply.sent`: 一个布尔值,如果响应已经发送则为 `true`。