Fastify 零基础学习手册
最近根据项目需求学习一下fastify框架,同步整理一下学习笔记记录。
目录
- [Fastify 零基础学习手册](#Fastify 零基础学习手册)
- 
- [Fastify 是什么?为什么选它?](#Fastify 是什么?为什么选它?)
- [每种语言都从最朴实的Hallo Word开始](#每种语言都从最朴实的Hallo Word开始)
- 
- [1. 初始化](#1. 初始化)
- [2. 新建 app.js](#2. 新建 app.js)
- [3. 启动 & 测试](#3. 启动 & 测试)
 
- 官方脚手架:一步到位目录规范
- 
- [1. 脚手架脚本](#1. 脚手架脚本)
- [2. 目录结构](#2. 目录结构)
 
- 关键核心理念
- 
- [1. 路由(Routes)](#1. 路由(Routes))
- [2. 插件(Plugins)](#2. 插件(Plugins))
- [3. Hook(生命周期钩子)](#3. Hook(生命周期钩子))
- [4. Schema(校验与序列化)](#4. Schema(校验与序列化))
- [5. 日志(Pino)](#5. 日志(Pino))
 
- 项目基础功能
- 
- [1. 数据库连接 [Database](https://www.fastify.cn/docs/latest/Guides/Database/)](#1. 数据库连接 Database)
 
- 注意点(避坑点)
 
Fastify 是什么?为什么选它?
| 特性 | 简述 | 
|---|---|
| 性能 | 路由+序列化预编译,官方 benchmark 30k+ req/s | 
| 插件化 | 任何功能皆可写成插件,天然作用域隔离 | 
| TypeScript | 内置类型声明,零成本 TS | 
| 日志 | 基于 Pino,开发/生产开箱即用 | 
| 校验&序列化 | JSON Schema 驱动,自动校验请求/响应 | 
每种语言都从最朴实的Hallo Word开始
1. 初始化
            
            
              bash
              
              
            
          
          mkdir fastify-demo && cd fastify-demo
npm init -y
npm install fastify2. 新建 app.js
            
            
              typescript
              
              
            
          
          const fastify = require('fastify')({ logger: true });
fastify.get('/', async (req, reply) => {
  return { hello world };
});
const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
start();3. 启动 & 测试
执行项目启动脚本,浏览器打开 http://localhost:3000
            
            
              shell
              
              
            
          
          node app.js官方脚手架:一步到位目录规范
1. 脚手架脚本
脚手架已集成 nodemon、ESLint、tap,可直接开发。
            
            
              shell
              
              
            
          
          npx fastify-cli generate .
// 如果执行时出现
npm install
npm run dev        # 带热重载如果执行时出现如下问题,表示当前目录已经存在 package.json,fastify-cli 为了避免覆盖,要求你显式地加一个 --integrate 参数,以用于把生成的内容整合进已有项目
            
            
              shell
              
              
            
          
          npx fastify-cli generate . --integratenpx : NPM 包运行器,用于在不同的 Node.js 版本上执行运行脚本和运行本地的二进制文件. 在 npm@5.2.0 版本以上可用.
成功示例
2. 目录结构
            
            
              html
              
              
            
          
          .
├── app.js                 # 应用入口
├── package.json
├── plugins/               # 全局插件
│   ├── sensible.js        # 错误处理
│   └── support.js         # 工具函数
├── routes/                # 自动加载的路由
│   ├── example/
│   │   └── index.js       # /example 路由
│   └── root.js            # / 路由
├── test/                  # 测试
│   ├── example.test.js
│   └── root.test.js
└── README.md关键核心理念
1. 路由(Routes)
路由配置为根据routes目录中对应文件夹名+方法中注册路由名称组合形式
            
            
              typescript
              
              
            
          
          // routes/user.js
module.exports = async function (fastify, opts) {
  fastify.get('/users/:id', async (req, reply) => {
    const { id } = req.params;
    return { id, name: 'Tom' };
  });
};注册:
            
            
              typescript
              
              
            
          
          fastify.register(require('./routes/user'));2. 插件(Plugins)
插件 = 可复用功能单元。
示例:把数据库连接写成插件 plugins/db.js
            
            
              typescript
              
              
            
          
          const fp = require('fastify-plugin');
module.exports = fp(async (f, opts) => {
  f.register(require('@fastify/postgres'), {
    connectionString: 'postgres://user:pwd@localhost:5432/db'
  });
});3. Hook(生命周期钩子)
            
            
              typescript
              
              
            
          
          fastify.addHook('onRequest', async (req, reply) => {
  req.startTime = Date.now();
});
fastify.addHook('onResponse', async (req, reply) => {
  req.log.info({ responseTime: Date.now() - req.startTime });
});4. Schema(校验与序列化)
            
            
              typescript
              
              
            
          
          const getUserSchema = {
  params: { type: 'object', properties: { id: { type: 'number' } } },
  response: {
    200: {
      type: 'object',
      properties: { id: { type: 'number' }, name: { type: 'string' } }
    }
  }
};
fastify.get('/users/:id', { schema: getUserSchema }, async (req, reply) => {
  const { id } = req.params;
  return { id, name: 'Tom' };
});5. 日志(Pino)
            
            
              typescript
              
              
            
          
          const fastify = require('fastify')({
  logger: { level: 'info' }   // 开发 debug,生产 warn
});项目基础功能
1. 数据库连接 Database
这里用 pgsql 和 redis 为例
pgsql
            
            
              shell
              
              
            
          
          npm i pg @fastify/postgres
            
            
              typescript
              
              
            
          
          1. 单文件闭包写法
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}`)
})
2. esm写法
module.exports = async function (fastify, opts) {
  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)
      }
    )
  })
}redis
            
            
              shell
              
              
            
          
          npm i @fastify/redis
            
            
              typescript
              
              
            
          
          1. 单文件闭包写法
'use strict'
const fastify = require('fastify')()
fastify.register(require('@fastify/redis'), { host: '127.0.0.1' })
// or
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}`)
})
2. esm写法
  fastify.register(require('@fastify/redis'), {
    url: 'redis://127.0.0.1:51865',
    connectTimeout: 2000,
    maxRetriesPerRequest: 1,
    enableOfflineQueue: false,
    lazyConnect: true
  })
	fastify.post('/saveRedis', function (req, reply) {
	    if (!fastify.redis) return reply.code(503).send({ error: 'redis unavailable' })
	    const { redis } = fastify
	    redis.set(req.body.key, req.body.value, (err) => {
	      reply.send(err || { status: 'ok' })
	    })
	  })
  fastify.get('/getRedis', function (req, reply) {
  if (!fastify.redis) return reply.code(503).send({ error: 'redis unavailable' })
  const { redis } = fastify
  redis.get(req.query.key, (err, val) => {
    reply.send(err || val)
  })注意点(避坑点)
- 请求格式支持(Content-Type)
 Fastify 原生只支持 'application/json' 和 'text/plain' content type。默认的字符集是 utf-8。如果你需要支持其他的 content type,你需要使用 addContentTypeParser API。默认的 JSON 或者纯文本解析器也可以被更改或删除。
注:假如你决定用 Content-Type 自定义 content type,UTF-8
便不再是默认的字符集了。请确保如下包含该字符集:text/html; charset=utf-8。

