Conditions:
validation via Joi
using Typescript
Express server
SWAGGER at / api-docs
Task: DRY
Decision:
First you need to decide what comes first: the Joi scheme, Swagger or the TypeScript interface. It has been empirically established that Joi should be made primary.
1. Installing all modules on Express
npm install --save swagger-ui-express
Add lines to app.ts (index.ts):
import swaggerUI = require('swagger-ui-express')
import swDocument from './openapi'
...
app.use('/api-docs',swaggerUI.serve,swaggerUI.setup(swDocument))
2. Create ./openapi.ts
This file contains basic information about the server. You can create it (like all the schemes below) using the SWAGGER utility. It is important to choose the openapi v3.0.0 protocol
:
import {swLoginRoute} from './routes/login'
const swagger = {
openapi: '3.0.0',
info: {
title: 'Express API for Dangle',
version: '1.0.0',
description: 'The REST API for Dangle Panel service'
},
servers: [
{
url: 'http://localhost:3001',
description: 'Development server'
}
],
paths: {
...swLoginRoute
},
}
export default swagger
.
3.
openapi-
./routes/login/index.ts:
import {swGetUserInfo} from './get-user-info'
import {swUpdateInfo} from './update-info'
export const swLoginRoute = {
"/login": {
"get": {
...swGetUserInfo
},
"patch": {
...swUpdateInfo
}
}
}
/login, : get patch. get-user-into.ts update-info.ts. .
4.
, Joi-.
.
: , .
update-info.ts, ( ):
import schema, {joiSchema} from './update-info.spec/schema'
export const swUpdateInfo = {
"summary": "update the user info",
"tags": [
"login"
],
"parameters": [
{
"name": "key",
"in": "header",
"schema": {
"type": "string"
},
"required": true
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
...schema
}
}
}
},
"responses": {
"200": {
"description": "Done"
},
"default": {
"description": "Error message"
}
}
}
// ...
JSON- Swagger-, . :
"schema": {
...schema
}
.
Joi- :
await joiSchema.validateAsync(req.body)
4. Joi-
Joi:
npm install --save joi joi-to-swagger
:
const joi = require('joi')
const j2s = require('joi-to-swagger')
// Joi
export const joiSchema = joi.object().keys({
mode: joi.string().required(),
email: joi.string().email()
})
// end of Joi
const schema = j2s(joiSchema).swagger
export default schema
Joi- swagger-.
, SWAGGER- . TypeScript-
5. TypeScript
npm install --save-dev gulp @babel/register @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-typescript
Gulp. , . gulpfile.ts :
const gulp = require('gulp')
const through = require('through2')
import { compile } from 'json-schema-to-typescript'
const fs = require('fs')
const endName = "schema.ts"
const routes = `./routes/**/*.spec/*${endName}`
function path(str: string) : string
{
let base = str
if(base.lastIndexOf(endName) != -1)
base = base.substring(0, base.lastIndexOf(endName))
return base
}
gulp.task('schema', () => {
return gulp.src(routes)
.pipe(through.obj((chunk, enc, cb) => {
const filename = chunk.path
import(filename).then(schema => { // dynamic import
console.log('Converting', filename)
compile(schema.default, `IDTO`)
.then(ts => {
//console.log(path(filename).concat('interface.ts'), ts)
fs.writeFileSync(path(filename).concat('interface.ts'), ts)
})
})
cb(null, chunk)
}))
})
// watch service
const { watch, series } = require('gulp')
exports.default = function() {
watch(routes, series('schema'))
}
The script bypasses all subdirectories named * .spec inside the directory from the router. There it looks for files with the names * schema.ts and creates a number of files with the names * interface.ts
Conclusion
Of course, these large and complex JSON objects with the openAPI specification are scary, but you need to understand that they are not written by hand, but generated by the SWAGGER utility.
Due to inexperience, the initial adjustment of the mechanism can take some time, but this will lead to savings of hundreds of hours that could be spent on routine!