365 lines
11 KiB
Markdown
365 lines
11 KiB
Markdown
# 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`。
|