原文地址:https://dev.to/desmondsanctity/documenting-nodejs-api-using-swagger-4klp 作者:Desmond Obisi
API 文档是开发软件的重要组成部分。它是一本说明手册,解释如何使用 API 及其服务。该手册可能包含教程、代码示例、屏幕截图以及任何有助于用户更好地理解如何使用 API 的内容。
在本文中,我们将学习如何使用一种名为 Swagger 的工具来记录使用 Node.js 编写的 API。Swagger 允许你描述 API 的结构,以便机器可以读取它们。API 能够描述自己的结构是 Swagger 的所有优点的根源。为什么它如此好呢?通过阅读我们的 API 结构,Swagger 可以自动构建美观且交互式的 API 文档。它还可以自动生成许多语言的 API 客户端库,并探索自动化测试等其他可能性。Swagger 通过要求我们的 API 返回一个包含整个 API 详细描述的 YAML 或 JSON 文件来实现这一点。该文件实质上是我们 API 的资源列表,符合 OpenAPI 规范。
使用 Node.js 和 Express 构建 API
为了开始编写 API 规范,我们将使用 Node.js 构建我们的 API,Node.js 是一个后端 JavaScript 运行时环境,它在 V8 JavaScript 引擎上运行,并在 Web 浏览器之外执行 JavaScript 代码。为了简单起见,我已经设置了项目,并且可以从这个 GitHub 存储库中克隆。为了在我们的本地机器上运行后端,我们将按照以下步骤进行操作:
- 创建一个新的文件夹用于项目,并在根文件夹中运行以下命令来克隆存储库
git clone https://github.com/DesmondSanctity/node-js-swagger.git
- 要成功运行代码,我们将需要一个数据库连接。我使用了 MongoDB Atlas 集群作为数据库,我们可以按照这个教程来设置一个,它非常简单。设置完成后,我们将获得我们的 URL,这是我们从应用程序连接到数据库所需的全部内容。
- 我们使用 JSON Web Token(JWT)来验证对 API 的访问权限,因此我们将生成一个密钥,该密钥将由我们的应用程序用于发送和验证请求。为了生成密钥,我们将在终端的任何位置运行以下命令。此脚本将生成一个随机的 64 位 ASCII 字符串,可用于加密 JWT 令牌。
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
- 现在,我们将创建一个名为
.env
的文件,我们将在其中将我们的 MongoDB Atlas 集群 URL 和 JWT 密钥存储为环境变量。文件应如下所示:
JWT_SECRET=<your JWT secret>
ATLAS_URI=<your MongoDB Atlas cluster URL>
- 现在,我们准备好运行应用程序了,但首先,我们将安装一些软件包,然后启动应用程序。如果你之前克隆了 GitHub 存储库,你只需要运行以下命令:
npm install // 安装必要的软件包
npm start // 启动应用程序
- 如果你在这一点上成功了,你将在终端中看到以下消息
> mini-blog@1.0.0 start
> nodemon server.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Database Connected
Server connected to http://localhost:8080
添加 Swagger UI 和配置
现在我们的 API 准备好了,我们将开始为其定义 Swagger 规范。我们可以通过两种方式构建 API 的 Swagger 文档:
- 在路由文件中手动编写规范,或者在应用程序中的专用 json 或 yaml 文件中编写规范。
- 使用现有的开发人员工具或软件包自动生成文档。
在本教程中,我们将使用手动方法来确保定义和规范的准确性。首先,我们将使用以下命令将两个名为"swagger-jsdoc"
和"swagger-ui-express"
的软件包作为依赖项进行安装:
npm install swagger-jsdoc swagger-ui-express --save-dev
安装完成后,我们将在应用程序的根目录中创建一个名为swagger.js
的新文件,并将以下代码粘贴到其中:
import swaggerJsdoc from 'swagger-jsdoc'import swaggerUi from 'swagger-ui-express'const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Mini Blog API',
description: "API endpoints for a mini blog services documented on swagger",
contact: {
name: "Desmond Obisi",
email: "info@miniblog.com",
url: "https://github.com/DesmondSanctity/node-js-swagger"
},
version: '1.0.0',
},
servers: [
{
url: "http://localhost:8080/",
description: "Local server"
},
{
url: "<your live url here>",
description: "Live server"
},
]
},
// looks for configuration in specified directoriesapis: ['./router/*.js'],
}
const swaggerSpec = swaggerJsdoc(options)
function swaggerDocs(app, port) {
// Swagger Page
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
// Documentation in JSON format
app.get('/docs.json', (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.send(swaggerSpec)
})
}
export default swaggerDocs
从代码中我们可以看到,我们定义了我们将在文档中使用的 Open API 规范(OAS):关于 API 的信息或细节、我们将公开 API 的服务器以及 Swagger 应该在我们的应用程序中的哪些路由中查找每个 API 的规范。
我们还可以看到我们的swaggerDocs
函数,它允许应用程序实例和端口能够使用我们之前安装的swaggerUi
和swaggerJsdoc
软件包生成文档,并在/docs
路由上提供服务。我们还可以使用/docs.json
获取 JSON 格式。最后,我们将更新我们的server.js
文件,包括我们的swaggerDocs
函数,以便在每次运行项目时始终生成和更新我们的文档。
在编写每个端点的规范之前,最后一步是将swaggerDocs
函数添加到我们的server.js
文件中,以便在应用程序启动时初始化 Swagger。
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import dotenv from 'dotenv';
import connect from './database/conn.js';
import userRouter from './router/user.js';
import postRouter from './router/post.js';
import swaggerDocs from './swagger.js'
dotenv.config()
const app = express();
/** middlewares */
app.use(express.json());
app.use(cors());
app.use(morgan('tiny'));
app.disable('x-powered-by'); // less hackers know about our stack
const port = process.env.PORT || 8080;
/** HTTP GET Request */
app.get('/', (req, res) => {
res.status(201).json("Home GET Request");
});
/** api routes */
app.use('/api/user', userRouter)
app.use('/api/post', postRouter)
/** start server only when we have valid connection */connect().then(() => {
try {
app.listen(port, () => {
console.log(`Server connected to http://localhost:${port}`);
})
swaggerDocs(app, port)
} catch (error) {
console.log('Cannot connect to the server')
}
}).catch(error => {
console.log("Invalid database connection...!");
})
编写 API 规范
现在是主要任务,我们将编写 Swagger 将用于为我们生成文档的 API 规范。我们目前在应用程序中有两个控制器和路由文件,用于user
和post
。我们的控制器包含应用程序的逻辑,而路由以请求的形式将端点和有效负载传递给控制器。让我们开始定义规范!
对于 User 路由器,我们将首先在路由文件user.js
中导入我们将需要的软件包:
import { Router } from "express";
const userRouter = Router();
/** import all controllers /import as controller from '../controllers/userController.js';
import Auth from '../middleware/auth.js';
/** All the API routes comes here*/export default userRouter;
然后,我们将为每个请求类型(GET、POST、PUT 和 DELETE)编写规范。规范是嵌入在我们要记录的路由的开头的 yaml 文件。需要注意的一些关键点是:
- Open API 规范实例-写在 yaml 文件的开头。
- API 端点-我们将用于请求的 URL。
- 请求类型-指示它是 GET、POST、PUT 还是 DELETE 请求。
- 请求体-用于将有效负载传递给 API。
- 参数-我们通过 URL 或参数传递给后端的数据。
- 内容类型-我们发送的内容类型。它会传递给 HTTP 头。
- 模式-它包含我们请求体的类型、所需字段以及请求体可以接受的属性。
- 响应-我们进行的 API 调用的结果,它告诉我们是否失败或成功,并报告错误。
POST:
/** POST Methods *//**
* @openapi
* '/api/user/register':
* post:
* tags:
* - User Controller
* summary: Create a user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* default: johndoe
* email:
* type: string
* default: johndoe@mail.com
* password:
* type: string
* default: johnDoe20!@
* responses:
* 201:
* description: Created
* 409:
* description: Conflict
* 404:
* description: Not Found
* 500:
* description: Server Error
*/userRouter.route('/register').post(controller.register); // register user/**
* @openapi
* '/api/user/login':
* post:
* tags:
* - User Controller
* summary: Login as a user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* default: johndoe
* password:
* type: string
* default: johnDoe20!@
* responses:
* 201:
* description: Created
* 409:
* description: Conflict
* 404:
* description: Not Found
* 500:
* description: Server Error
*/userRouter.route('/login').post(controller.verifyUser,controller.login); // login in app/**
* @openapi
* '/api/user/verify':
* post:
* tags:
* - User Controller
* summary: Verify a user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* properties:
* username:
* type: string
* default: johndoe
* responses:
* 201:
* description: Created
* 409:
* description: Conflict
* 404:
* description: Not Found
* 500:
* desccription: Server Error
*
/userRouter.route('/verify').post(controller.verifyUser, (req, res) => res.end()); // authenticate user
GET:
/** GET Methods *//**
* @openapi
* '/api/user/{username}':
* get:
* tags:
* - User Controller
* summary: Get a user by username
* parameters:
* - name: username
* in: path
* description: The username of the user
* required: true
* responses:
* 200:
* description: Fetched Successfully
* 400:
* description: Bad Request
* 404:
* description: Not Found
* 500:
* description: Server Error
*/
userRouter.route('/:username').get(controller.getUser) // user with username
PUT:
/** PUT Methods *//**
* @openapi
* '/api/user/update':
* put:
* tags:
* - User Controller
* summary: Modify a user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - userId
* properties:
* userId:
* type: string
* default: ''
* firstName:
* type: string
* default: ''
* lastName:
* type: string
* default: ''
* responses:
* 200:
* description: Modified
* 400:
* description: Bad Request
* 404:
* description: Not Found
* 500:
* description: Server Error
*/
userRouter.route('/update').put(controller.updateUser); // is use to update the user profile
DELETE:
/** DELETE Methods *//**
* @openapi
* '/api/user/{userId}':
* delete:
* tags:
* - User Controller
* summary: Delete user by Id
* parameters:
* - name: userId
* in: path
* description: The unique Id of the user
* required: true
* responses:
* 200:
* description: Removed
* 400:
* description: Bad request
* 404:
* description: Not Found
* 500:
* description: Server Error
*
/userRouter.route('/:userId').delete(controller.deleteUser);
在 Swagger 上测试 API
在完成我们的 API 文档后,我们应该能够查看 Swagger 文档并使用它来测试我们的 API。如果你按照之前的步骤进行操作,你应该会看到以下类似的界面。我们的文档将在/docs
路由上提供服务。
我们将使用 Swagger 文档 UI 进行一些请求并查看结果。
创建用户:
创建帖子:
正如我们从上面的示例中看到的那样,我们可以使用 Swagger 文档来测试我们的 API,以在数据库中创建、读取和修改数据。这有助于使 API 易于理解和集成。根据这些示例,我们可以继续测试其他请求方法,如使用 PUT 方法更新用户或帖子,使用 GET 方法读取用户或帖子,以及使用 DELETE 方法从数据库中删除它们。
总结
最后,我们可以得出结论,API 文档是软件开发周期中非常重要的一部分,它有助于协作并使用户体验无缝。Swagger 的一些优点包括但不限于:
- 将 API 文档与服务器和客户端同步。
- 允许我们生成 REST API 文档并与 REST API 进行交互。使用 Swagger UI 框架与 REST API 进行交互可以清楚地了解 API 如何响应参数。
- 提供 JSON 和 XML 格式的响应。
- 适用于各种技术的实现。
使用 Apifox 管理 API 文档
Swagger 管理接口有时很不方便,缺乏团队间的分享协作,所以我更推荐使用 Apifox。
Apifox 是一个比 Postman 更强大的接口测试工具,Apifox = Postman + Swagger + Mock + JMeter,Apifox 支持调试 http(s)、WebSocket、Socket、gRPC、Dubbo 等协议的接口,并且集成了 IDEA 插件。在开发完接口后,可以通过 Apifox 的 IDEA 插件一键生成接口文档,多端同步,非常方便测试和维护。
将 Swagger 导出为 JSON
如下图所示,选择“Convert and save as JSON”,将 Swagger 文档导出为 JSON 文件。
将 Swagger 文件导入 Apifox
打开 Apifox,创建一个项目后,选择“项目设置->导入数据->OpenAPI/Swagger->文件导入”,将已导出的 Swagger 格式的 JSON 文件导入即可。
导入时,会有预览,可以选择导入全部,也可以选择性的导入接口。
导入成功之后,就可以选择一个环境来测试接口。如下图所示,接口成功返回数据:
知识扩展: