Создание самодокументирующегося сервера на Node.JS

Условия:

  • валидация через Joi

  • использование Typescript

  • Express сервер

  • SWAGGER на /api-docs

Задача: DRY

Решение:

Для начала необходимо решить что первично: схема Joi, Swagger или TypeScript интерфейс. Эмпирическим путём установлено что первичной стоит сделать Joi.

1. Установка всех модулей на Express

npm install --save swagger-ui-express

Добавить строки в app.ts (index.ts):

import swaggerUI = require('swagger-ui-express')
import swDocument from './openapi'
...
app.use('/api-docs',swaggerUI.serve,swaggerUI.setup(swDocument))

2. Создать ./openapi.ts

В этом файле содержится основные сведения о сервере. Создать его (как и все схемы, приведённые ниже) можно с помощью SWAGGER-утилиты. Важно выбрать при этом протокол openapi v3.0.0

Пример содержимого:

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'))
}

Скрипт обходит все подкаталоги с названием *.spec внутри каталога с роутера. Там он ищет файлы с именами *schema.ts и создаёт рядом файлы c именами *interface.ts

Заключение

Разумеется, эти большие и сложные JSON-объекты с openAPI-спецификацией пугают, но надо понимать, что они не пишутся вручную, а геренятся SWAGGER-утилитой.

Из-за неопытности первичная настройка механизма может занять какое-то время, но это приведет к экономии сотен часов, которые могли бы быть потрачены на рутину!

Let’s block ads! (Why?)

Read More

Recent Posts

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

3 часа ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

10 часов ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

13 часов ago

Нейросети будут писать тексты объявления за продавцов на Авито

У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…

14 часов ago

Объявлены победители международной премии Workspace Digital Awards-2024

24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…

1 день ago

Яндекс проведет гик-фестиваль Young Con

27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…

2 дня ago