作为 web 开发者,对构建高可用 REST API 应该并不陌生。

高可用 API 有如下衡量标准:

  • 速度(API 响应时间)
  • 文档(清晰简洁的文档,能够很好的描述 API)
  • 架构和持续性(代码可维护可扩展)

这篇教程我们将要通过 Node.js、MongoDB、Fastify 和 Swagger 搭建高可用后端架构。

源代码已经托管到了 GitHub 3 上。

0

开始之前

你应该具有初级\中极 JavaScript 知识,了解 Node.js 和 MongoDB,知道什么是 REST APIS。

下面是相关链接:

涉及的技术

可以在标签页中打开这些链接,随时查阅。

需要安装的环境:

除此之外,还需要 IDE 和 终端,我在 Mac 用的是 iTerm2 ,在 Windows 上用的是 Hyper

让我们开始吧

1_Jw9V__6jYhm2amP74D_0lw

打开终端初始化项目,执行下面的代码:

mkdir fastify-api
cd fastify-api
mkdir src
cd src
touch index.js
npm init

上面的代码,创建了两个新目录,然后切换到了新建的路径下,创建了一个 index.js 文件并通过 npm 初始化项目。

在初始化新项目的时候会提示输入一些值,这些值可以留空在稍后更新。

完成后,src 目录下生成了 package.json。在这个文件里你可以更改工程初始化时留空的值。

接下来安装将要用到的所有的依赖库:

npm i nodemon mongoose fastify fastify-swagger boom

下面是在它们的官网引用的介绍:

nodemon

nodemon 是 node.js 应用的开发工具,当检测到目录下的文件改变时它会自动重启 node 应用。

使用 nodemon 不需要更改代码,它是 node 命令的封装,直接在执行脚本的时候用 nodemon替换 node 即可。

把下面的代码添加到 package.json 文件里来使用 nodemon:

"start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js"

这时 package.json 文件应该这样:

{
  "name": "fastify-api",
  "version": "1.0.0",
  "description": "A blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger.",
  "main": "index.js",
  "scripts": {
  "start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},
  "author": "Siegfried Grimbeek <siegfried.grimbeek@gmail.com> (www.siegfriedgrimbeek.co.za)",
  "license": "ISC",
  "dependencies": {
  "boom": "^7.2.2",
  "fastify": "^1.13.0",
  "fastify-swagger": "^0.15.3",
  "mongoose": "^5.3.14",
  "nodemon": "^1.18.7"
  }
}

mongoose

Mongoose 提供了一个直截了当、基于 schema 的应用数据 model 解决方案。它包括了内置类型转换、校验、构建查询、业务逻辑钩子等功能,可直接使用。

fastify 3

Fastify 是一个高度专注于以最少的开销和强大的插件架构提供最佳开发者体验的 web 框架。Fastify 受到Hapi 和 Express 的启发,是目前最快的 web 框架之一。

fastify-swagger 1

Swagger 1 文档生成器速度很快。它通过 routes 里声明的 schema 生成符合 swagger 规范的文档。

boom

boom 提供一个返回 HTTP 错误的工具集。

启动服务开始创建第一个路由

1_rocnESJrNWsRGXMygLfDCQ

index.js 里添加如下代码:

// Require the framework and instantiate it
const fastify = require('fastify')({
  logger: true
})

// Declare a route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

// Run the server!
const start = async () => {
  try {
    await fastify.listen(3000)
    fastify.log.info(`server listening on ${fastify.server.address().port}`)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

在这里引入了 Fastify 框架,声明了第一个 route 并在 port 3000 上启动了服务,代码很容易理解但是要注意初始化 Fastify 时传入了可选的对象:

// Require the fastify framework and instantiate it
const fastify = require('fastify')({
  logger: true
})

上面的代码开启了默认关闭的 Fastify 内置日志功能。

在终端的 src 目录下运行下面的代码:

npm start

浏览器访问 http://localhost:3000/ 会看到 {hello:world}

回到 index.js 开始配置数据库。

启动 MongoDB 创建 model

1_Ce0gUe0LbnhL7ebnDGTp5w

安装 MongoDB,打开终端运行以下命令启动 MongoDB。

mongod

在 MongoDB 里不需要手动创建数据库。只需要在配置的时候指定数据库名,当数据存储的时候, MongoDB 会自动创建数据库。

index.js 里添加如下代码:

...
// Require external modules
const mongoose = require('mongoose')
// Connect to DB
mongoose.connect(‘mongodb://localhost/mycargarage’)
 .then(() => console.log(‘MongoDB connected…’))
 .catch(err => console.log(err))
...

在上面的代码里我们引入了 Mongoose 来连接 MongoDB 数据库。数据库名是 mycargarage,如果一切顺利,在终端里会看到 MongoDB connected...

注意不需要手动重启 app,因为我们之前添加了 Nodemon 库。

现在数据库已经运行,可以创建第一个 Model 了。在 src 路径下创建一个名为 models 的新文件夹,在里面创建一个 Car.js 文件,添加如下代码:

// External Dependancies
const mongoose = require('mongoose')

const carSchema = new mongoose.Schema({
  title: String,
  brand: String,
  price: String,
  age: Number,
  services: {
    type: Map,
    of: String
  }
})

module.exports = mongoose.model('Car', carSchema)

上面的代码声明的 carSchema 包含了和 cars 相关的所有信息。除了两个基本数据类型: StringNumber。还用到了相对比较陌生的 Map,可以在这里 2阅读更多。接着导出了 carSchema,以便在 app 里使用。

也可以把 routes、controllers 和 config 的代码都放到 index.js 文件里,但是为了代码可维护,这里每个组件都有它自己的文件夹。

创建 car controller

src 下创建 controllers 文件夹,在文件夹里创建 carController.js 文件:

// External Dependancies
const boom = require('boom')

// Get Data Models
const Car = require('../models/Car')

// Get all cars
exports.getCars = async (req, reply) => {
  try {
    const cars = await Car.find()
    return cars
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Get single car by ID
exports.getSingleCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = await Car.findById(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Add a new car
exports.addCar = async (req, reply) => {
  try {
    const car = new Car(req.body)
    return car.save()
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Update an existing car
exports.updateCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = req.body
    const { ...updateData } = car
    const update = await Car.findByIdAndUpdate(id, updateData, { new: true })
    return update
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Delete a car
exports.deleteCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = await Car.findByIdAndRemove(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}

carController.js

代码看起来有点长,其实很简单。

  • 引入了 boom 来处理错误:boom.boomify(err)
  • 导出了所有的函数以便在 route 里引入。
  • 每个函数都是异步的,使用了 await 表达式,这样在执行异步函数的时会候暂停运行直到程序调用传入的 Promise 的 resolve,然后继续异步函数的执行并返回 resolved 值。点击点解更多
  • 每个函数代码都在 try/catch 语句里。点击了解更多
  • 每个函数都有两个参数:req (request) 和 reply。教程里只用到了 request 参数,用来访问 request body、 参数以及处理数据。点击了解更多

注意代码 31 行:

const car = new Car({ …req.body })

用到了扩展运算符。点击了解更多

注意代码 42 行:

const { …updateData } = car

使用了 JavaScript 解构赋值和扩展运算符。点击了解更多

另外,代码还用到了一些标准的 Mongoose 方法来操作数据库。

可能你已经迫不及待的想要运行你的 API 测试一下了,但是在开始之前,还需要把 controller 连接到 routes,把 routes 连接到 app 上。

创建导入 routes

同样,在项目根目录下创建 routes 文件夹。在文件夹内创建 index.js 文件,文件代码如下:

// Import our Controllers
const carController = require('../controllers/carController')

const routes = [
  {
    method: 'GET',
    url: '/api/cars',
    handler: carController.getCars
  },
  {
    method: 'GET',
    url: '/api/cars/:id',
    handler: carController.getSingleCar
  },
  {
    method: 'POST',
    url: '/api/cars',
    handler: carController.addCar,
    // schema: documentation.addCarSchema
  },
  {
    method: 'PUT',
    url: '/api/cars/:id',
    handler: carController.updateCar
  },
  {
    method: 'DELETE',
    url: '/api/cars/:id',
    handler: carController.deleteCar
  }
]

module.exports = routes

在这里我们引入了 controller 并且把每个函数分发到了相应的 routes 上。

每个 route 都是由 method、url、和 handler 组成,访问路由会调用相应 app 函数。

上面 routes 里出现的的 :id 是向路由里传递参数的一个常见做法,它允许以下面这种形式传入 id:

http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323

文件建立关联,测试 API

现在大部分功能都已完成,需要做的只是把他们连接起来,启动 API 服务。导入 routes,在 index.js 文件里添加如下代码:

const routes = require(‘./routes’)

接下来需要遍历 routes 数组,然后在 Fastify 里面初始化。可以用下面的代码实现,、在 index.js 文件里添加:

routes.forEach((route, index) => {
 fastify.route(route)
})

现在准备好测试了!

API 测试有一个很棒的工具 Postman。我们在request 的 body 里以 raw object 做为参数。

查询所有 cars:

1_YoxRE054Q7qgrAxaCgrzLw

查询指定 car:

1_YoxRE054Q7qgrAxaCgrzLw

添加新 car:

1_YoxRE054Q7qgrAxaCgrzLw

services 看起来是空的,实际上数据已经添加到了数据库里了

更新 car:

1_YoxRE054Q7qgrAxaCgrzLw

删除 car:

1_YoxRE054Q7qgrAxaCgrzLw

现在一个全功能 API 已具备,还差文档,别怕,用 Swagger 可以方便的生成。

1_7iaXjYojG6kWxLTZaW1x4Q

添加 Swagger

现在创建 config 文件夹。在里面创建 swagger.js 文件,文件代码如下:

exports.options = {
  routePrefix: '/documentation',
  exposeRoute: true,
  swagger: {
    info: {
      title: 'Fastify API',
      description: 'Building a blazing fast REST API with Node.js, MongoDB, Fastify and Swagger',
      version: '1.0.0'
    },
    externalDocs: {
      url: 'https://swagger.io',
      description: 'Find more info here'
    },
    host: 'localhost',
    schemes: ['http'],
    consumes: ['application/json'],
    produces: ['application/json']
  }
}

上面的代码是将要传入到 fastify-swagger 插件的 option object,在index.js 里添加如下代码:

// Import Swagger Options
const swagger = require(‘./config/swagger’)
// Register Swagger
fastify.register(require(‘fastify-swagger’), swagger.options)

在启动服务之后还需要添加如下代码:

...
await fastify.listen(3000)
fastify.swagger()
fastify.log.info(`listening on ${fastify.server.address().port}`)
...

如果一切顺利访问 http://localhost:3000/documentation,效果如下:

1_HV-5eImCMs7LtiLodTz7CQ

是不是很简单?现在有了随着 API 自动更新的文档,可以任意在 routes 里面添加信息了点击此处阅读更多

接下来

现在基本 API 已经成型,可以扩展成能想象的任何应用,有一万种可能。

接下在的教程里,我们将要集成 GraphQL,最后会集成前端的 Vue.js。

原文链接:How to build blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger,作者:Siegfried Grimbeek