Fastify

起步

Hello!感谢你来到 Fastify 的世界!
这篇文档将向你介绍 Fastify 框架及其特性,也包含了一些示例和指向其他文档的链接。
那,这就开始吧!

安装

使用 npm 安装:

  1. npm i fastify --save

使用 yarn 安装:

  1. yarn add fastify

第一个服务器

让我们开始编写第一个服务器吧:

  1. // 加载框架并新建实例
  2. const fastify = require('fastify')({
  3. logger: true
  4. })
  5. // 声明路由
  6. fastify.get('/', function (request, reply) {
  7. reply.send({ hello: 'world' })
  8. })
  9. // 启动服务!
  10. fastify.listen(3000, function (err, address) {
  11. if (err) {
  12. fastify.log.error(err)
  13. process.exit(1)
  14. }
  15. fastify.log.info(`server listening on ${address}`)
  16. })

更喜欢使用 async/await?Fastify 对其提供了开箱即用的支持。
(我们还建议使用 make-promises-safe 来避免文件描述符 (file descriptor) 及内存的泄露)

  1. const fastify = require('fastify')({
  2. logger: true
  3. })
  4. fastify.get('/', async (request, reply) => {
  5. return { hello: 'world' }
  6. })
  7. const start = async () => {
  8. try {
  9. await fastify.listen(3000)
  10. } catch (err) {
  11. fastify.log.error(err)
  12. process.exit(1)
  13. }
  14. }
  15. start()

如此简单,棒极了!
可是,一个复杂的应用需要比上例多得多的代码。当你从头开始构建一个应用时,会遇到一些典型的问题,如多个文件的操作、异步引导,以及代码结构的布置。
幸运的是,Fastify 提供了一个易于使用的平台,它能帮助你解决不限于上述的诸多问题!

本文档中的示例,默认情况下只监听本地 127.0.0.1 端口。要监听所有有效的 IPv4 端口,需要将代码修改为监听 0.0.0.0,如下所示:

  1. fastify.listen(3000, '0.0.0.0', function (err, address) {
  2. if (err) {
  3. fastify.log.error(err)
  4. process.exit(1)
  5. }
  6. fastify.log.info(`server listening on ${address}`)
  7. })

类似地,::1 表示只允许本地的 IPv6 连接。而 :: 表示允许所有 IPv6 地址的接入,当操作系统支持时,所有的 IPv4 地址也会被允许。

当使用 Docker 或其他容器部署时,使用 0.0.0.0:: 会是最简单的暴露应用的方式。

第一个插件

就如同在 JavaScript 中一切皆为对象,在 Fastify 中,一切都是插件 (plugin)。
在深入之前,先来看看插件系统是如何工作的吧!
让我们新建一个基本的服务器,但这回我们把路由 (route) 的声明从入口文件转移到一个外部文件。(参阅路由声明)。

  1. const fastify = require('fastify')({
  2. logger: true
  3. })
  4. fastify.register(require('./our-first-route'))
  5. fastify.listen(3000, function (err, address) {
  6. if (err) {
  7. fastify.log.error(err)
  8. process.exit(1)
  9. }
  10. fastify.log.info(`server listening on ${address}`)
  11. })
  1. // our-first-route.js
  2. async function routes (fastify, options) {
  3. fastify.get('/', async (request, reply) => {
  4. return { hello: 'world' }
  5. })
  6. }
  7. module.exports = routes

这个例子调用了 register API,它是 Fastify 框架的核心,也是添加路由、插件等的唯一方法。

在本文的开头,我们提到 Fastify 提供了帮助应用异步引导的基础功能。为什么这一功能十分重要呢? 考虑一下,当存在数据库操作时,数据库连接需要在服务器接受外部请求之前完成。该如何解决这一问题呢?
典型的解决方案是使用复杂的回调函数或 Promise,但如此会造成框架的 API、其他库以及应用程序的代码混杂在一起。
Fastify 则不走寻常路,它从本质上用最轻松的方式解决这一问题!

让我们重写上述示例,加入一个数据库连接。

首先,安装 fastify-pluginfastify-mongodb

  1. npm i --save fastify-plugin fastify-mongodb

server.js

  1. const fastify = require('fastify')({
  2. logger: true
  3. })
  4. fastify.register(require('./our-db-connector'))
  5. fastify.register(require('./our-first-route'))
  6. fastify.listen(3000, function (err, address) {
  7. if (err) {
  8. fastify.log.error(err)
  9. process.exit(1)
  10. }
  11. fastify.log.info(`server listening on ${address}`)
  12. })

our-db-connector.js

  1. const fastifyPlugin = require('fastify-plugin')
  2. async function dbConnector (fastify, options) {
  3. fastify.register(require('fastify-mongodb'), {
  4. url: 'mongodb://localhost:27017/test_database'
  5. })
  6. }
  7. // 用 fastify-plugin 包装插件,以使插件中声明的装饰器、钩子函数暴露在根作用域里。
  8. module.exports = fastifyPlugin(dbConnector)

our-first-route.js

  1. async function routes (fastify, options) {
  2. const collection = fastify.mongo.db.collection('test_collection')
  3. fastify.get('/', async (request, reply) => {
  4. return { hello: 'world' }
  5. })
  6. fastify.get('/animals', async (request, reply) => {
  7. const result = await collection.find().toArray()
  8. if (result.length === 0) {
  9. throw new Error('No documents found')
  10. }
  11. return result
  12. })
  13. fastify.get('/animals/:animal', async (request, reply) => {
  14. const result = await collection.findOne({ animal: request.params.animal })
  15. if (result === null) {
  16. throw new Error('Invalid value')
  17. }
  18. return result
  19. })
  20. }
  21. module.exports = routes

哇,真是快啊!
介绍了一些新概念后,让我们回顾一下迄今为止都做了些什么吧。
如你所见,我们可以使用 register 来注册数据库连接器或者路由。 这是 Fastify 最棒的特性之一了!它使得插件按声明的顺序来加载,唯有当前插件加载完毕后,才会加载下一个插件。如此,我们便可以在第一个插件中注册数据库连接器,并在第二个插件中使用它。(参见 这里 了解如何处理插件的作用域)。 当调用函数 fastify.listen()fastify.inject()fastify.ready() 时,插件便开始加载了。

我们还用到了 decorate API。现在来看看这一 API 是什么,以及它是如何运作的吧。 考虑下需要在应用的不同部分使用相同的代码或库的场景。一种解决方案便是按需引入这些代码或库。哈,这固然可行,但却因为重复的代码和麻烦的重构让人苦恼。
为了解决上述问题,Fastify 提供了 decorate API。它允许你在 Fastify 的命名空间下添加自定义对象,如此一来,你就可以在所有地方直接使用这些对象了。

更深入的内容,例如插件如何运作、如何新建,以及使用 Fastify 全部的 API 去处理复杂的异步引导的细节,请看插件指南

插件加载顺序

为了保证应用的行为一致且可预测,我们强烈建议你采用以下的顺序来组织代码:

  1. └── 来自 Fastify 生态的插件
  2. └── 你自己的插件
  3. └── 装饰器
  4. └── 钩子函数
  5. └── 你的服务应用

这确保了你总能访问当前作用域下声明的所有属性。
如前文所述,Fastify 提供了一个可靠的封装模型,它能帮助你的应用成为单一且独立的服务。假如你要为某些路由单独地注册插件,只需复写上述的结构就足够了。

  1. └── 来自 Fastify 生态的插件
  2. └── 你自己的插件
  3. └── 装饰器
  4. └── 钩子函数
  5. └── 你的服务应用
  6. └── 服务 A
  7. └── 来自 Fastify 生态的插件
  8. └── 你自己的插件
  9. └── 装饰器
  10. └── 钩子函数
  11. └── 你的服务应用
  12. └── 服务 B
  13. └── 来自 Fastify 生态的插件
  14. └── 你自己的插件
  15. └── 装饰器
  16. └── 钩子函数
  17. └── 你的服务应用

验证数据

数据的验证在我们的框架中是极为重要的一环,也是核心的概念。
Fastify 使用 JSON Schema 验证来访的请求。 让我们来看一个验证路由的例子:

  1. const opts = {
  2. schema: {
  3. body: {
  4. type: 'object',
  5. properties: {
  6. someKey: { type: 'string' },
  7. someOtherKey: { type: 'number' }
  8. }
  9. }
  10. }
  11. }
  12. fastify.post('/', opts, async (request, reply) => {
  13. return { hello: 'world' }
  14. })

这个例子展示了如何向路由传递配置选项。选项中包含了一个名为 schema 的对象,它便是我们验证路由所用的模式 (schema)。借由 schema,我们可以验证 bodyquerystringparams 以及 header
请参阅验证与序列化获取更多信息。

序列化数据

Fastify 对 JSON 提供了优异的支持,极大地优化了解析 JSON body 与序列化 JSON 输出的过程。
在 schema 的选项中设置 response 的值,能够加快 JSON 的序列化 (没错,这很慢!),就像这样:

  1. const opts = {
  2. schema: {
  3. response: {
  4. 200: {
  5. type: 'object',
  6. properties: {
  7. hello: { type: 'string' }
  8. }
  9. }
  10. }
  11. }
  12. }
  13. fastify.get('/', opts, async (request, reply) => {
  14. return { hello: 'world' }
  15. })

一旦指明了 schema,序列化的速度就能达到原先的 2-3 倍。这么做同时也保护了潜在的敏感数据不被泄露,因为 Fastify 仅对 schema 里出现的数据进行序列化。 请参阅 验证与序列化获取更多信息。

扩展服务器

Fastify 生来十分精简,也具有高可扩展性。我们相信,一个小巧的框架足以实现一个优秀的应用。
换句话说,Fastify 并非一个面面俱到的框架,它依赖于自己惊人的生态系统

测试服务器

Fastify 并没有提供测试框架,但是我们推荐你在测试中使用 Fastify 的特性及结构。
更多内容请看测试

从命令行启动服务器

感谢 fastify-cli,它让 Fastify 集成到了命令行之中。

首先,你得安装 fastify-cli:

  1. npm i fastify-cli

你还可以加入 -g 选项来全局安装它。

接下来,在 package.json 中添加如下行:

  1. {
  2. "scripts": {
  3. "start": "fastify start server.js"
  4. }
  5. }

然后,创建你的服务器文件:

  1. // server.js
  2. 'use strict'
  3. module.exports = async function (fastify, opts) {
  4. fastify.get('/', async (request, reply) => {
  5. return { hello: 'world' }
  6. })
  7. }

最后,启动你的服务器:

  1. npm start

幻灯片与视频 (英文资源)