使用Swagger记录Node.js API
#javascript #node #api #swagger

介绍

API文档是开发软件不可或缺的一部分。这是一本说明手册,解释了如何使用API​​及其服务。本手册可能包含教程,代码示例,屏幕截图以及其他任何可以帮助用户更好地了解如何使用API​​的教程。

在本文中,我们将学习如何使用名为Swagger的工具记录在Node.js中编写的API。 Swagger使您可以描述API的结构,以便机器可以阅读它们。 API描述自己的结构的能力是Swagger中所有令人敬畏的根源。为什么这么好?好吧,通过阅读我们的API结构,Swagger可以自动构建美丽而互动的API文档。它还可以自动以多种语言为您的API生成客户库库,并探索其他可能性,例如自动测试。 Swagger通过要求我们的API返回包含整个API的详细说明的YAML或JSON来做到这一点。该文件本质上是我们的API的资源清单,它遵守OpenAPI Specifications

用node.js构建我们的API和Express

要开始编写API规格,我们将使用Node.js构建API,这是一个后端JavaScript运行时环境,该环境在V8 JavaScript引擎上运行,并在Web浏览器外执行JavaScript代码。为了简单起见,我已经设置了该项目,并且可以从这个GitHub repository克隆。为了使后端在本地机器上运行,我们将遵循以下步骤:

  • 为项目创建一个新文件夹,然后在根文件夹中运行此命令以克隆存储库
git clone https://github.com/DesmondSanctity/node-js-swagger.git
  • 要成功运行代码,我们将需要一个数据库连接。我在数据库中使用了mongodb atlas群集,我们可以遵循此tutorial进行设置,设置非常简单。设置后,我们将获得URL,这就是我们需要从应用程序连接到数据库的全部。
  • 我们正在使用JSON Web令牌(JWT)来验证对API的访问,因此我们将生成一个秘密密钥,我们的应用程序将使用该密钥来发送和验证请求。为了产生它,我们将在终端中的任何位置运行此命令。该脚本将生成一个随机的64位ASCII字符串,可用于加密JWT令牌。
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  • 现在,我们将创建一个名为.env的文件,我们将在其中存储MongoDB Atlas cluster URL和JWT Secret作为环境变量。文件应该看起来像这样:
    JWT_SECRET=<your JWT secret>
    ATLAS_URI=<your MongoDB Atlas cluster URL>
  • 现在,我们准备运行该应用程序,但首先,我们将安装一些软件包,然后启动应用程序。如果您之前将GitHub repository克隆,则只需要在下面运行以下命令:
    npm install   // To install the neccessary packages
    npm start    //  To start the application
  • 如果您此时成功,您将在终端中看到以下消息
    > 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 Specs。我们可以通过两种方式为我们的API构建大大肆宣传文档:

  • 在我们的应用程序中手动编写路由器文件中的规格或专用的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 directories
      apis: ['./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 Specification (OAS)的代码中查看:有关API的信息或详细信息,我们将曝光的服务器以及Swagger应该寻找规格的应用程序中的服务器以及应用程序中的路由对于我们的每个API。

我们还可以看到我们的swaggerDocs函数,该功能允许应用程序实例和端口能够生成文档,以使用我们之前安装的swaggerUiswaggerJsdoc软件包,并在/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规格

现在,到了主要任务,我们将为API编写Swagger将用来为我们生成文档的规格。目前,我们的userpost的应用程序中有两个控制器和路由器文件。当路由器以请求的形式将端点和有效载荷携带给控制器时,我们的控制器包含该应用程序的逻辑。让我们开始定义规格!

对于用户路由器,我们将首先导入路由器文件中需要的软件包15:

    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;

然后,我们将编写每个请求类型的规格,即获取,发布,放置和删除。该规范是我们要记录的路线开头嵌入的YAML文件。需要注意的一些要点是:

  • 打开API规范实例写在YAML文件的开头。
  • API端点我们将用于请求。
  • 请求类型指示是否是get,张贴,放置或删除请求。
  • 请求主体将有效载荷传递给API。
  • 参数数据我们通过URL或参数到后端
  • 内容类型我们发送的内容类型。它传递给HTTP标头。
  • 模式包含我们的请求主体的类型,所需字段和请求主体可以接受的属性。
  • 响应我们发出的API呼叫的结果,它告诉我们它是否失败或成功,也报告了错误。

帖子:

    /** 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 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 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);

在招摇上测试我们的API

完成API文档后,我们应该能够查看我们的Swagger文档并使用它来测试我们的API。如果您遵循到这一点,则应该像以下一份一样的视图。我们的文档在/docs路线上提供。

Swagger UI documentation

我们将使用Swagger文档UI提出一些请求,并查看结果。

创建一个用户:

A POST request to create a user

创建帖子:

A POST request to create a post

从上面的示例中看到,我们可以使用Swagger文档来测试我们的API,在数据库中创建,读取和修改数据。这有助于使API可以理解和易于集成。按照以下示例,我们可以继续测试其他请求方法,例如放置以更新用户或发布,阅读用户或发布并删除以从数据库中删除它们。

结论

我们最终可以得出结论,API文档是软件开发周期中非常重要的一部分,它有助于协作并使用户体验无缝。 Swagger的一些优点包括但不限于:

  • 以相同的速度将API文档与服务器和客户端同步。
  • 允许我们生成REST API文档并与REST API进行交互。使用Swagger UI框架与REST API的相互作用可以清楚地了解API对参数的响应。
  • 提供JSON和XML的格式的响应。
  • 实施可用于各种技术。