Presumably no web developer is a stranger to REST APIs and the challenges that architecting an effective and efficient API solution brings.
大概没有Web开发人员对REST API和架构有效的API解决方案带来的挑战陌生。
These challenges include:
这些挑战包括:
In this tutorial we are going to address all of the above using a combination of Node.js, MongoDB, Fastify and Swagger.
在本教程中,我们将结合使用Node.js , MongoDB , Fastify和Swagger解决以上所有问题。
The source code for the project is available on GitHub.
You should have some beginner/intermediate JavaScript knowledge, have heard of Node.js and MongoDB, and know what REST APIs are.
您应该具有一些初学者/中级JavaScript知识 ,听说过Node.js和MongoDB,并且知道什么是REST API 。
Below are some links to get you updated:
以下是一些可让您更新的链接:
It is a good idea to open the above pages in new tabs, for easy reference.
最好在新标签页中打开上述页面,以方便参考。
You will also need an IDE and a terminal, I use iTerm2 for Mac and Hyper for Windows.
您还将需要一个IDE和一个终端,我在Mac上使用iTerm2 ,在Windows上使用Hyper 。
Initialise a new project by opening your terminal, executing each of the following lines of code:
通过打开您的终端并执行以下每行代码来初始化一个新项目:
mkdir fastify-api
cd fastify-api
mkdir src
cd src
touch index.js
npm init
In the above code, we created two new directories, navigated into them, created an index.js
file and initialed our project via npm.
在上面的代码中,我们创建了两个新目录,进入其中,创建了index.js
文件,并通过npm初始化了我们的项目。
You will be prompted to enter several values when initialising a new project, these you can leave blank and update at a later stage.
初始化新项目时,系统将提示您输入多个值,您可以将这些值留空并在以后进行更新。
Once completed, a package.json file is generated in the src
directory. In this file you can change the values entered when the project was initialised.
完成后,将在src
目录中生成一个package.json文件。 在此文件中,您可以更改项目初始化时输入的值。
Next we install all the dependancies that we will need:
接下来,我们将安装所需的所有依赖项 :
npm i nodemon mongoose fastify fastify-swagger boom
Below is a brief description of what each package does, quoted from their respective websites:
下面是每个程序包的简要说明,引用自它们各自的网站:
nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
nodemon是一种工具,可在检测到目录中的文件更改时通过自动重新启动节点应用程序来帮助开发基于node.js的应用程序。
nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
nodemon是一种工具,它可以在检测到目录中的文件更改时通过自动重新启动节点应用程序来帮助开发基于node.js的应用程序。
nodemon does not require any additional changes to your code or method of development. nodemon is a replacement wrapper for
node
, to usenodemon
replace the wordnode
on the command line when executing your script.nodemon不需要对您的代码或开发方法进行任何其他更改。 nodemon是
node
的替换包装器,可以在执行脚本时使用nodemon
替换命令行上的wordnode
。
To set up nodemon, we need to add the following line of code to our package.json
file, in the scripts object:
要设置nodemon ,我们需要在scripts对象中将以下代码行添加到package.json
文件中:
“start”: “./node_modules/nodemon/bin/nodemon.js ./src/index.js”,
Our package.json
file should now look as follows:
现在,我们的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 provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box.
Mongoose提供了一个直接的,基于模式的解决方案来对您的应用程序数据进行建模。 它包括现成的内置类型转换,验证,查询构建,业务逻辑挂钩等等。
Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.
Fastify是一个Web框架,高度专注于以最少的开销和强大的插件体系结构提供最佳的开发人员体验。 它受到Hapi和Express的启发,据我们所知,它是该镇中最快的Web框架之一。
Swagger documentation generator for Fastify. It uses the schemas you declare in your routes to generate a swagger compliant doc.
Swagger文档生成器,用于Fastify。 它使用您在路线中声明的架构来生成符合标准的文档。
boom provides a set of utilities for returning HTTP errors.
boom提供了一组用于返回HTTP错误的实用程序。
Add the following code to your index.js
file:
将以下代码添加到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()
We require the Fastify framework, declare our first route and initialise the server on port 3000
, the code is pretty self explanatory but take note of the options object passed when initialising Fastify:
我们需要Fastify框架,声明我们的第一个路由并在port 3000
上初始化服务器,代码是很容易解释的,但是请注意初始化Fastify时传递的options对象:
// Require the fastify framework and instantiate it
const fastify = require('fastify')({
logger: true
})
The above code enables Fastify’s built in logger which is disabled by default.
上面的代码可启用Fastify的内置记录器,默认情况下该记录器处于禁用状态。
You can now run the follow code in your src
directory in your terminal:
现在,您可以在终端的 src
目录中运行以下代码:
npm start
Now when you navigate to http://localhost:3000/ you should see the {hello:world}
object returned.
现在,当您导航到http:// localhost:3000 /时 ,应该看到返回了{hello:world}
对象。
We will get back to the index.js
file but for now let’s move on to setting up our database.
我们将回到index.js
文件,但现在让我们继续建立数据库。
Once MongoDB has been successfully installed, you can open a new terminal window and start up a MongoDB instance by running the following:
成功安装MongoDB之后,您可以打开一个新的终端窗口并通过运行以下命令来启动MongoDB实例:
mongod
With MongoDB, we do not need to create a database. We can just specify a name in the setup and as soon as we store data, MongoDB will create this database for us.
使用MongoDB ,我们不需要创建数据库。 我们只需在设置中指定一个名称,一旦我们存储数据, MongoDB就会为我们创建此数据库。
Add the following to your index.js
file:
将以下内容添加到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))
...
In the above code we require Mongoose and connect to our MongoDB database. The database is called mycargarage
and if all went well, you will now see MongoDB connected...
in your terminal.
在上面的代码中,我们需要Mongoose并连接到我们的MongoDB数据库。 该数据库称为mycargarage
,如果一切顺利,您现在将在终端中看到MongoDB connected...
Notice that you did not have to restart the app, thanks to the Nodemon
package that we added earlier.
请注意,由于我们之前添加了Nodemon
软件包,因此不必重启应用程序。
Now that our database is up and running, we can create our first Model. Create a new folder within the src
directory called models
, and within it create a new file called Car.js
and add the following code:
现在我们的数据库已启动并正在运行,我们可以创建第一个模型。 在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)
The above code declares our carSchema
that contains all the information related to our cars. Apart from the two obvious data types: String
and Number
. We also make use of a Map
which is relatively new to Mongoose and you can read more about it here. We then export our carSchema
to be used within our app.
上面的代码声明了我们的carSchema
,其中包含与我们的汽车有关的所有信息。 除了两种明显的数据类型: String
和Number
。 我们还使用了Mongoose相对较新的Map
,您可以在此处阅读有关此内容的更多信息。 然后,我们导出carSchema
以在我们的应用程序中使用。
We could proceed with setting up our routes, controllers and config in the index.js
file, but part of this tutorial is demonstrating a sustainable codebase. Therefore each component will have its own folder.
我们可以继续在index.js
文件中设置路由,控制器和配置,但是本教程的一部分将演示可持续的代码库。 因此,每个组件将具有其自己的文件夹。
To get started with creating the controllers, we create a folder in the src
directory called controllers
, and within the folder, we create a carController.js
file:
为了开始创建控制器,我们在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)
}
}
The above may seem like a little much to take in, but it is actually really simple.
上面的内容似乎有些不足,但实际上确实很简单。
We require boom to handle our errors: boom.boomify(err)
.
我们需要boom来处理错误: boom.boomify(err)
。
Each function is an async function that can contain an await expression that pauses the execution of the async function and waits for the passed Promise’s resolution, and then resumes the async function’s execution and returns the resolved value. Learn more here.
每个函数都是一个异步函数,其中可以包含一个await表达式,该表达式会暂停异步函数的执行并等待所传递的Promise的分辨率,然后恢复异步函数的执行并返回解析后的值。 在这里了解更多。
Each function is wrapped in a try / catch statement. Learn more here.
每个函数都包装在try / catch语句中。 在这里了解更多。
Each function takes two parameters: req
(the request) and reply
(the reply). In our tutorial we only make use of the request parameter. We will use it to access the request body and the request parameters, allowing us to process the data. Learn more here.
每个函数都有两个参数: req
(请求)和reply
(答复)。 在我们的教程中,我们仅使用request参数。 我们将使用它来访问请求正文和请求参数,从而使我们能够处理数据。 在这里了解更多。
Take note of the code on line 31:
注意第31行的代码:
Take note of the code on line 31:const car = new Car({ …req.body })
注意第31行的代码: const car = new Car({ …req.body })
This makes use of the
这利用了
JavaScript spread operator. Learn more here.
JavaScript传播运算符。 在这里了解更多。
Take note of the code on line 42:
注意第42行的代码:
Take note of the code on line 42:const { …updateData } = car
注意第42行的代码: const { …updateData } = car
This makes use of the
这利用了
JavaScript destructuring in conjunction with the spread operator. Learn more here.
JavaScript与传播运算符一起进行解构。 在这里了解更多。
Other than that, we make use of some standard Mongoose features used to manipulate our database.
除此之外,我们利用一些标准的Mongoose功能来操作数据库。
You are probably burning to fire up your API and do a sanity check, but before we do this, we just need to connect the controller to the routes and then lastly connect the routes to the app.
您可能正在努力启动API并进行完整性检查,但是在执行此操作之前,我们只需要将控制器连接到路由 ,然后最后将路由连接到应用程序即可。
Once again, we can start by creating a folder in the root directory of our project, but this time, it is called routes
. Within the folder, we create an index.js
file with the following code:
再一次,我们可以在项目的根目录中创建一个文件夹,但是这次,它被称为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
Here we are requiring our controller and assigning each of the functions that we created in our controller to our routes.
在这里,我们需要控制器 ,并将在控制器中创建的每个功能分配给路线。
As you can see, each route consists out of a method, a url and a handler, instructing the app on which function to use when one of the routes is accessed.
如您所见,每条路由均由方法,URL和处理程序组成,它们指示应用程序在访问其中一条路由时使用哪个函数。
The :id
following some of the routes is a common way to pass parameters to the routes, and this will allow us to access the id as follows:
某些路由之后的:id
是将参数传递给路由的一种常用方法,这将使我们能够按以下方式访问ID :
http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323
http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323
Now that we have most of our parts constructed, we just need to connect them all together to start serving data via our API. Firstly we need to import our routes that we created by adding the following line of code to our main index.js
file:
现在我们已经构造了大部分部件,我们只需要将它们连接在一起就可以开始通过我们的API提供数据。 首先,我们需要通过将以下代码行添加到主index.js
文件中来导入创建的路由 :
const routes = require(‘./routes’)
We then need to loop over our routes array to initialise them with Fastify. We can do this with the following code, which also needs to be added to the main index.js
file:
然后,我们需要遍历路由数组以使用Fastify对其进行初始化。 我们可以使用以下代码来完成此操作,还需要将其添加到主index.js
文件中:
routes.forEach((route, index) => {
fastify.route(route)
})
Now we are ready to start testing!
现在我们准备开始测试!
The best tool for the job is Postman, which we will use to test all of our routes. We will be sending our data as raw objects in the body of the request and as parameters.
最好的工具是Postman ,我们将使用它来测试所有路线。 我们将在请求正文中将数据作为原始对象和参数发送。
Finding all cars:
查找所有汽车:
Finding a single car:
寻找一辆车:
Adding a new car**:
添加新车**:
** The services appear to be empty, but the information does in fact persist to the database.
**服务似乎是空的,但实际上信息确实保留在数据库中。
Updating a car:
更新汽车:
Deleting a car:
删除汽车:
We now have a fully functional API — but what about the documentation? This is where Swagger is really handy.
现在我们有了一个功能齐全的API,但是文档呢? 这是Swagger真正方便的地方。
Now we will create our final folder called config. Inside we will create a file called swagger.js
with the following code:
现在,我们将创建名为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']
}
}
The above code is an object with the options which we will pass into our fastify-swagger plugin. To do this, we need to add the following to our index.js
file:
上面的代码是一个带有选项的对象,这些选项将传递给我们的fastify-swagger插件。 为此,我们需要将以下内容添加到我们的index.js
文件中:
// Import Swagger Options
const swagger = require(‘./config/swagger’)
// Register Swagger
fastify.register(require(‘fastify-swagger’), swagger.options)
And then we need to add the following line after we have initialised our Fastify server:
然后,在初始化Fastify服务器之后,需要添加以下行:
...
await fastify.listen(3000)
fastify.swagger()
fastify.log.info(`listening on ${fastify.server.address().port}`)
...
And that is it! If you now navigate to http://localhost:3000/documentation, you should see the following:
就是这样! 如果现在导航到http:// localhost:3000 / documentation ,则应该看到以下内容:
As simple as that! You now have self updating API documentation that will evolve with your API. You can easily add additional information to your routes, see more here.
就如此容易! 现在,您将拥有与API一起发展的自我更新API文档。 您可以轻松地向路线添加其他信息,请参见此处 。
Now that we have a basic API in place, the possibilities are limitless. It can be used as the base for any app imaginable.
现在我们已经有了一个基本的API,可能性是无限的。 它可以用作任何可以想象的应用程序的基础。
In the next tutorial, we will integrate GraphQL and eventually integrate the frontend with Vue.js too!
在下一个教程中,我们将集成GraphQL并最终将前端与Vue.js集成!