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 fastify
2. 新建 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 . --integrate
npx : 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。