Ir para o conteúdo principal

Swagger: melhores abordagens para documentação

Definição da melhor abordagem para utilização do Swagger em aplicações Node.js

OBJETIVO

Definição da melhor abordagem para padronização de documentações de APIs node.js usando o Swagger no âmbito da Superintendência de Tecnologia da Informação e Comunicação - SETIC.

 

JUSTIFICATIVA

Atualmente, as API's são documentadas usando o Postman que, como tem integração com a _rout_, basta apenas algumas marcações no código-fonte para que a documentação seja gerada. Todavia, com a mudança do Postman para o Swagger, aplicação adotada como padrão no âmbito da SETIC, visando a implementação de dicionário de dados, exemplos de resposta variados abordando diversos casos e também a documentação de erros possíveis, faz-se necessário definir padrões de documentação que não polua o código excessivamente e de fácil manutenção/geração.

 
RESULTADOS ESPERADOS

 

Maior produtividade na confecção da documentação com o uso de funções já existentes no Typescript, sem prejuízo da manutenção da aplicação, pois o código permanece 'limpo'.

Com base nos estudos realizados decidimos utilizar o swagger, diversos times hoje usam esta ferramenta para documentação, logo, tonaria mais fácil a utilização para os mesmos.

 

Swagger UI Express:

Nos testes  realizados constatamos que esta biblioteca serve como pilar para as demais que tramalham com Swagger, a configuração é simples porem a escrita de documentação exige muita escrita tornando a manutenção árdua e suscetível a erro, oferece a opção de documentação em forma de comentários no código mas que se prova inviável pois polui visualmente o documento tornando difícil a compreensão e manutenção do mesmo.

 

Express jsdoc swagger:

Biblioteca que utiliza a 'swagger UI Express' citada anteriormente, segue a mesma premissa de documentação através de escrita de comentários no código e apresenta os mesmos problemas, poluição visual e dificuldade de manutenção.

 

Routing Controller Openapi:

Biblioteca que também utiliza a 'swagger UI Express', porem trás uma nova abordagem utilizando funções existentes no Typescript, usando decoradores podemos definir tipos de retornos, retornos múltiplos, casos onde ocorrem erros e exemplos de resposta, com o auxilio de outra biblioteca, 'class-validator-jsonschema', podemos definir schema de dados que nos ajuda a não repetir código e também oferecer um dicionário de dados.

 

Escolha:

Dentre as diversas bibliotecas Javascript de documentação que trabalham com swagger, optamos por utilizar uma que não polua o código excessivamente e de fácil manutenção, dentre as diversas opções escolhemos a routing-controller-openapi por sua compatibilidade com uma biblioteca que já usamos e também por sua:

  • Simplicidade;
  • Facilidade de implementação;
  • Facilidade de manutenção; e,
  • Pouca poluição do código.
 
ENVOLVIDOS
  • Assessor:
    • Diego Gonçalves de Almeida.
  • Equipe Técnica:
    • Diego Barros de Oliveira;
    • Alef Carvalho da Silva; e,
    • Anderson Soares Cardoso.
  • Gerente de Desenvolvimento:
    • Janderson de Castro Thomaz.
  • Product Owner:
    • Jônatas Justiniano Lima.
  • Scrum Master:
    • Edson Masami Hiraçaka.

 

GLOSSÁRIO
  • Swagger: é uma aplicação para auxilio no desenvolvimento de documentações
  • OpenAPI: é uma especificação para documentação de API's Rest agnóstica a linguagem.
  • Node.js - é um ambiente de execução JavaScript open-source que funciona utilizando o mecanismo Chrome V8.
  • JavaScript - é uma linguagem de programação de alto nível que segue as especificações ECMAScript.
  • TypeScript - é um superconjunto sintático estrito de Javascript, criado e mantido pela Microsoft.
  • yarn - é um gerenciador de pacotes javascript.
  • NPM - Node Package Manager.

 

IMPLEMENTAÇÃO:DESENVOLVIMENTO

Swagger UI Express:

Nos testes  realizados constatamos que esta biblioteca serve como pilar para as demais que tramalham com Swagger, a configuração é simples porem a escrita de documentação exige muita escrita tornando a manutenção árdua e suscetível a erro, oferece a opção de documentação em forma de comentários no código mas que se prova inviável pois polui visualmente o documento tornando difícil a compreensão e manutenção do mesmo.

Exemplo de implementação Swagger UI Express

yarn add swagger-ui-express
const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');

var options = {
  swaggerOptions: {
    validatorUrl: null
  }
};

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
/**
 * @swagger
 * /:
 *     get:
 *          summary: Lista de produtos
 *          description: Lista de produtos usada para popular a grid e produtos
 *          responses:
 *              200:
 *                  description: lista
 *                  content:
 *                      application/json:
 *                          schema:
 *                              type: array
 *                              items:
 *                                  type: array
 *                                  items:
 *                                      type: object
 *                                      properties:
 *                                          id:
 *                                              type: string
 *                                              description: id do produto
 *                                              example: '0002321'
 *                                          categoria_id:
 *                                              type: string
 *                                              description: id da categoria
 *                                              example: '0254'
 *                                          ncm_id:
 *                                              type: string
 *                                              description: id do ncm
 *                                              example: '329'
 *                                          nome:
 *                                              type: string
 *                                              description: nome do produto
 *                                              example: Produto teste
 *                                          inativo?:
 *                                              type: number
 *                                              description: flag que indica se o produto esta ativo (1 ou null)
 *                                              example: 1
 */
route.get('/', (req, res) => {
	...
})

 

Express jsdoc swagger:

Biblioteca que utiliza a 'swagger UI Express' citada anteriormente, segue a mesma premissa de documentação através de escrita de comentários no código e apresenta os mesmos problemas, poluição visual e dificuldade de manutenção.

 

Routing Controller Openapi:

Biblioteca que também utiliza a 'swagger UI Express', porem trás uma nova abordagem utilizando funções existentes no Typescript, usando decoradores podemos definir tipos de retornos, retornos múltiplos, casos onde ocorrem erros e exemplos de resposta, com o auxilio de outra biblioteca, 'class-validator-jsonschema', podemos definir schema de dados que nos ajuda a não repetir código e também oferecer um dicionário de dados.

Segue a baixo um exemplo de implementação com a routing-controllers-openapi em um ambiente de teste. 

    Instalação:

    yarn add routing-controllers-openapi
    
    # Dependências
    yarn add class-validator-jsonschema # para criar dicionários de dados
    yarn add swagger-ui-express # para servir a documentação no swagger

     

    Configuração:

    // importação das bibliotecas
    import swagger from "swagger-ui-express";
    import { routingControllersToSpec } from "routing-controllers-openapi";
    import { validationMetadatasToSchemas } from "class-validator-jsonschema";
    
    // configuração do class-validator-jsonschema
    const schemas = validationMetadatasToSchemas({
      refPointerPrefix: "#/components/schemas/",
    });
    
    // recupera metadata das rotas
    const storage = getMetadataArgsStorage();
    
    // gera as especificações da documentação no padrão OpenAPI
    const spec = routingControllersToSpec(
      storage,
      {},
      {
        components: { schemas },
      },
    );
    
    app.use("/doc", swagger.serve, swagger.setup(spec)); // cria uma rota para servir a documentação
    

     

    Exemplo de implementação:

    import { IsNumber, IsString } from "class-validator";
    import { JSONSchema } from "class-validator-jsonschema";
    
    const example1 = {
      id: "001",
      nome: "Example",
      idade: 32,
    };
    
    const example2 = {
      id: "001",
      nome: "Example",
      idade: null,
    };
    
    @JSONSchema({ examples: [example1, example2] })
    export class User {
      @IsString()
      id!: string;
    
      @IsString()
      nome!: string;
    
      @IsNumber()
      idade?: number;
    }
    
    
    
    import { Body, Delete, Get, HttpCode, JsonController, Param, Post, Put } from "routing-controllers";
    import { OpenAPI, ResponseSchema } from "routing-controllers-openapi";
    import { ApplicationError } from "../error/ApplicationError";
    import { UserService } from '../services/user.service';
    import { User } from "../models/User.schema";
    
    @JsonController("/users")
    export class UserController {
      @Get("/")
      @ResponseSchema(User, { isArray: true })
      @ResponseSchema(ApplicationError, { statusCode: 400 })
      async list() {
        return await UserService.list()
      }
    
      @Get("/:userId")
      @ResponseSchema(User)
      @ResponseSchema(ApplicationError, { statusCode: 400 })
      async show(@Param("userId") userId: string): Promise<User> {
        return await UserService.getUserById(userId)
      }
    
      @HttpCode(201)
      @Post("/")
      @ResponseSchema(User)
      async store(@Body() user: User) {
    
        return await UserService.createUser(user);
      }
    
      @Put("/:userId")
      @ResponseSchema(User)
      async edit(@Param("userId") userId: string, @Body() user: User) {
        return user;
      }
    
      @Delete("/:userId")
      @HttpCode(204)
      @ResponseSchema("", { statusCode: 204 })
      @ResponseSchema(ApplicationError, { statusCode: 404 })
      async delete(@Param("userId") userId: string) {
        return await UserService.deleteUserById(userId)
      }
    }
    

     

    CONCLUSÃO

    AposDessa oforma time entrar em acordo, optamos pelaa utilização da 'routing-controller-openapi' pelase suamostrou fácilmais integraçeficaz dado que:

    • Simplicidade;
    • Facilidade de implementação;
    • Facilidade de manutenção; e,
    • Pouca poluição asdo tecnologiascódigo.
    • utilizadas
    • Uso quede utilizamosTypescript
    • e fácil manutenção.

     

    REFERÊNCIAS

    [1] SWAGGER UI EXPRESS. Disponível em: https://www.npmjs.com/package/swagger-ui-express. Acesso em: 18 ago. 2021.

     

    [2] SWAGGER JSDOC. Disponível em: https://www.npmjs.com/package/swagger-jsdoc.  Acesso em: 18 ago. 2021.

     

    [3] How to Document an Express API with Swagger UI and JSDoc. 2020. Disponível em: https://dev.to/kabartolo/how-to-document-an-express-api-with-swagger-ui-and-jsdoc-50do.  Acesso em: 18 ago. 2021.

     

    [4] EXPRESS JSDOC SWAGGER. Disponível em: https://www.npmjs.com/package/express-jsdoc-swagger.  Acesso em: 18 ago. 2021.

     

    [5] ROUTING CONTROLLER OPENAPI. Disponível em: https://github.com/epiphone/routing-controllers-openapi.  Acesso em: 18 ago. 2021.