從零開始的野路子React/Node(7)將Swagger(OpenAPI)運用於後端API
之前公司做專案是用過swagger來配置python模型的API,感覺非常好用。swagger可以提供request, response甚至error的驗證機制,十分便利。node當然也可以用啦。
我們需要使用的庫主要是swagger-ui-express,它將提供swagger的相關功能以及一個UI,方便檢視和除錯。
1、初始設定
老規矩,我們還是通過express work_with_swagger來新建一個叫work_with_swagger的專案。然後依舊是刪除bin資料夾,改改app.js以及將package.json中的start改為node app.js(當然,用nodemon會更好,可以安裝npm install nodemon,然後將start改為nodemon app.js)。
var express = require('express'); var app = express(); app.use(express.urlencoded({extended: true})); //解析json用 app.use(express.json()); //解析json用 app.listen(5000, function() { console.log('App listening on port 5000...') });
然後我們來做些設定:
我們會有一個Controller來負責request -> response的邏輯。
我們將原來的routes刪掉,增加自己的路由Routes,對接到對應的controller。
我們新增一個swagger資料夾,並在其中加入我們的配置檔案swagger.yml。
2、新增swagger-ui-express
現在,我們需要改動一下app.js來把swagger配置檔案包含進去。由於我們的swagger檔案是yaml檔案,所以我們需要yamljs庫來讀取它(預設只能讀取json格式的swagger檔案,直接抄https://www.npmjs.com/package/swagger-ui-express提供的方法)。
此外,我們把路由檔案也加入進去。
var express = require('express'); var swaggerUi = require('swagger-ui-express');var YAML = require('yamljs'); const swaggerDocument = YAML.load('./swagger/swagger.yml'); const RandomRouter = require('./Routes/RandomRouter'); var app = express(); app.use(express.urlencoded({extended: true})); //解析json用 app.use(express.json()); //解析json用 app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); app.use('/', RandomRouter) app.listen(5000, function() { console.log('App listening on port 5000...') });
我們現在可以象徵性地寫幾個controllers並把他們加入路由檔案中,2個get,1個post:
var express = require('express'); var YAML = require('yamljs'); exports.healthCheck = (req, res) => { const swaggerDocument = YAML.load('./swagger/swagger.yml'); res.status(200).send( { message:'耗子尾汁', version:swaggerDocument['info']['version'] } ) }; exports.setAge = (req, res) => { var age = req.params.number res.status(200).send(`來騙,來偷襲,我${age}歲的老同志`) }; exports.tricks = (req, res) => { var tricks = req.body.tricks var comment = req.body.comment var line = tricks.join(', ') + ', ' + comment res.status(200).send(line) };
var express = require('express'); const RandomController = require('../Controllers/RandomController'); var router = express.Router(); router.get('/age/:number', RandomController.setAge); router.post('/tricks', RandomController.tricks); router.get('/', RandomController.healthCheck); module.exports = router;
接下來,我們來配置一下swagger.yml(yaml檔案寫長了真是需要遊標卡尺……):
openapi: 3.0.0 info: title: Work with Swagger description: This is just an experiment version: v1.0 servers: - url: http://localhost:5000 paths: /: get: summary: Health check endpoint operationId: healthCheck responses: 200: description: Successful operation 400: description: Bad request 404: description: Not found /age/{number}: get: summary: Set the age of the user parameters: - name: number in: path description: The age of the user required: true schema: type: integer format: int64 responses: 200: description: Successful operation 400: description: Bad request 404: description: Not found /tricks: post: summary: Hit by some tricks requestBody: content: application/json: schema: $ref: '#/components/schemas/tricks' responses: 200: description: Successful operation 400: description: Bad request 404: description: Not found components: schemas: tricks: type: object required: - tricks - comment properties: tricks: type: array comment: type: string
這裡我們對每個路由節點做出了一些規定,比如對於/age/{number},我們規定number只能是整數;比如對於/tricks,傳入的json一定要包含tricks和comment等等(也就是規定了schema)。
這裡我們可以用2種不同的方式來規定schema:一種是直接在節點部分的schema下繼續下寫去(例如/age/{number}節點那樣);另一種是單獨寫在components的schemas下面,而在節點的schema處用$ref來指定引用(例如/tricks節點那樣)。
OK,差不多完工了,現在我們進入UI介面看一看效果,輸入http://localhost:5000/api-docs/,會出現這樣一個介面:
點開某個節點(此處為/age/{number}),就能看到我們對該節點的一些設定:
點選Try it out之後就可以輸入params,我們輸入一個整數試試,點選Execute:
如果我們試圖輸入string,會發現無法Execute:
看來效果不錯。但swagger-ui-express畢竟只是類似於一個文件工具,點到為止。我們在Postman中試試,會發現其實根本沒有驗證機制的存在……
3、補上驗證機制
雖然驗證要以和為貴,但不能光點到為止啊,看來我們還需要另一個驗證工具,那就是openapi-express-validator。
我們用npm install openapi-express-validator來完成安裝,然後再在app.js中加入一些內容:
var express = require('express'); var swaggerUi = require('swagger-ui-express'); var YAML = require('yamljs'); var OpenApiValidator = require('express-openapi-validator'); const swaggerDocument = YAML.load('./swagger/swagger.yml'); const RandomRouter = require('./Routes/RandomRouter'); var app = express(); app.use(express.urlencoded({extended: true})); //解析json用 app.use(express.json()); //解析json用 app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // 新增,指定一下swagger檔案 const spec = './swagger/swagger.yml'; app.use('/spec', express.static(spec)); // 將OpenApiValidator加入中介軟體 app.use( OpenApiValidator.middleware({ apiSpec: './swagger/swagger.yml', validateRequests: true, //validateResponses: true, // 如果需要驗證response,則設為true }), ); app.use('/', RandomRouter) app.use((err, req, res, next) => { // 配置錯誤資訊 res.status(err.status || 500).json({ message: err.message, errors: err.errors, }); }); app.listen(5000, function() { console.log('App listening on port 5000...') });
現在我們再來試試:
這次沒問題了,我們得到了一個錯誤資訊,提示我們params中的number應該是整數。
輸入整數則沒有問題,驗證功能完成了。
程式碼見: