Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Swagger: documentando APIs REST com OpenAPI

Swagger: documentando APIs REST com OpenAPI

Documentando APIs com Swagger UI - Escrevendo documentação com Swagger

Boas-vindas ao curso de documentação de API REST no padrão OpenAPI com o Swagger! Meu nome é Thiago Bussola e serei seu instrutor!

Audiodescrição: Thiago é um homem branco, de cabelos lisos e curtos na cor castanho-escuro com alguns fios grisalhos. Possui sobrancelhas grossas, barba, bigode e cavanhaque também na cor castanho-escuro. Usa um óculos de armação quadrada na cor preta e veste uma camiseta amarela. À sua frente, um microfone na altura do pescoço. Ao fundo, uma estante com livros e objetos decorativos.

O Padrão OpenAPI e a Estrutura do Curso

Uma das atividades que realizamos diariamente como pessoas desenvolvedoras é acessar documentações de API externas para integração. Vamos abrir uma página, por exemplo, da Giphy Developers, que o Discord e várias outras plataformas utilizam para buscar GIFs. Para implementar isso, há uma série de regras e endpoints que podemos utilizar, os quais são bastante interessantes.

Toda essa informação está documentada para facilitar o acesso de pessoas desenvolvedoras, clientes e pessoas responsáveis pelo produto, permitindo que compreendam como essa documentação funciona. Temos o exemplo da Poké API, que é bem conhecida e já fornece algumas informações, incluindo uma parte interativa e uma descrição do que cada rota faz.

A ideia é que possamos pegar uma documentação pronta, entender seu funcionamento interativo e, em seguida, adicionar algumas rotas. Como o padrão da OpenAPI tende a ser sempre o mesmo, trabalharemos com uma estrutura de documentação já pronta, que pode ser editada para nossas APIs.

Temos um projeto de API REST simples com duas entidades: user e books. Estamos realizando uma autenticação básica com JWT e temos um arquivo swagger.json que contém a documentação seguindo o padrão OpenAPI. Precisamos estar atentos a alguns detalhes do projeto, como a configuração do tsconfig.json. Habilitamos a flag resolveJsonModule: true para permitir a importação de arquivos JSON. Assim, se quisermos importar um arquivo .json, essa flag deve estar habilitada.

Instalação do Swagger

Temos uma página no localhost chamada api-docs, que ainda não está funcionando. Precisamos habilitar o Swagger na aplicação. A partir do terminal, vamos instalar o Swagger com o comando npm install swagger-ui-express.

npm install swagger-ui-express

Em seguida, instalamos @types/swagger-ui-express como dependência de desenvolvimento com npm install @types/swagger-ui-express --save-dev.

npm install @types/swagger-ui-express --save-dev

Configuração do Middleware

No arquivo de configuração da API, temos uma classe chamada app, onde configuramos os middlewares, a base de dados e os arquivos de rota. No middleware, configuramos o Express. Vamos adicionar this.express.use();.

private middleware(): void {
    this.express.use(express.json());
    this.express.use()
}

Antes de prosseguir, importamos a biblioteca swaggerUi e o arquivo JSON com import swaggerUi from 'swagger-ui-express'; e import swaggerJson from './swagger.json';.

import swaggerUi from 'swagger-ui-express';
import swaggerJson from './swagger.json';

Feito isso, podemos passar os seguintes parâmetros para this.express.use(): "/api-docs", swaggerUi.serve e swaggerUi.setup(swaggerJson).

private middleware(): void {
    this.express.use(express.json());
    this.express.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerJson));
}

Execução do Projeto com MongoDB

Estamos utilizando o banco de dados MongoDB. Então, para executar o projeto, estando na pasta principal, será necessário executar docker compose up -d no terminal para subir o banco de dados.

Voltamos à página de documentação, atualizamos e, após levantar o servidor com npm start, a documentação estará disponível. A documentação do Swagger é interativa, permitindo operações como deletar, atualizar e buscar livros.

Definição da Estrutura do Arquivo swagger.json

No arquivo swagger.json, especificamos que estamos usando o padrão 3.0 da OpenAPI. Se houver algo no trabalho que utilize uma versão diferente, o arquivo mudará. Para documentações novas, recomenda-se usar a versão mais recente, 3.0. Podemos dar um título à documentação, que será atualizado automaticamente. Assim, deixamos o arquivo pronto, realizando poucas alterações.

Para definir as rotas, abrimos uma tag chamada paths. Em seguida, especificamos o caminho /books e indicamos que pode haver um método post. O post terá a tag Books para separar a documentação.

No post, definimos um requestBody, cujo content será application/json, e incluímos um schema, definido mais abaixo, no components. No caso, temos um schema de Book para usar em todas as rotas que precisem de um exemplo. Aqui, definimos o exemplo, com um livro semi pronto para o usuário ver como cadastrar um livro, um autor e um ISBN.

As respostas já estão definidas de acordo com a API. No arquivo book.controller.ts, retornamos códigos como 200, 500, 201 e 400. Portanto, a documentação deve estar de acordo com o que foi escrito na API.

Temos também as rotas. No caso da busca por título, passamos o parâmetro title e temos books/{id}. Quando passamos por id, podemos fazer um put. Teremos parameters e também um requestBody. Se chamarmos a mesma rota books/{id} com o verbo delete, haverá um comportamento diferente na documentação.

Interação com a Documentação

Vamos ver essa documentação interagindo rapidamente. Acessamos a documentação e começamos a interagir com ela. Primeiro, abrimos a tag Books, onde podemos armazenar todas as rotas semelhantes. Se tivermos outros segmentos de rota, conseguimos separá-los de forma mais organizada.

Em seguida, abrimos Create a book e clicamos em Try it out para fazer uma requisição. Podemos cadastrar o livro do exemplo.

{
  "title": "The Great Gatsby",
  "author": "F. Scott Fitzgerald",
  "ISBN": "9780744066868"
}

Ao executar, o sistema informa que o livro foi cadastrado, retornando o id, já que estamos usando o MongoDB, e o status 201. No banco, usando o MongoDB Compass, verificamos na tabela books que o livro foi cadastrado.

Podemos cadastrar mais um e alterar o ISBN para 9780744066999:

{
  "title": "O Senhor dos Aneus",
  "author": "JRR Tolkien",
  "ISBN": "9780744066999"
}

Ao executar, o registro é cadastrado no banco.

Na parte de listagem, temos um Get all books. Podemos fazer um Try it out, executar e ver os dois livros cadastrados.

Na busca por títulos Find a book by title, podemos buscar por título. Clicamos em Try it out para liberar o parâmetro, buscamos por "O Senhor dos Aneis" e ele retorna corretamente o livro pesquisado.

Na tag de atualização do livro, Update a book, pegaremos o ID do primeiro livro, que já está na documentação, e faremos um Try it out para atualizá-lo. Vamos alterar o título para "Código Limpo", o autor para Robert Martin e o ISBN para qualquer valor.

{
  "title": "O Código Limpo",
  "author": "Robert Martin",
  "ISBN": "123654987"
}

Ao executar, a atualização é confirmada. Verificamos pela documentação e o livro "O Código Limpo" de Robert Martin está presente.

Vamos copiar o ID do livro atualizado e utilizar a tag de exclusão, Delete a book, para excluir o livro pela documentação. Após executar, confirmamos que apenas um registro permanece no banco. De volta à documentação, ao listar todos os livros, verificamos que há apenas um item no array.

Importância da Documentação Interativa

Essa é a forma de usar a documentação. Para novos integrantes da equipe ou pessoas de produto, a documentação permite interação e compreensão das regras de negócio da aplicação. Podemos adicionar descrições sobre parâmetros, funcionamento e respostas, tornando a documentação amigável para quem a utiliza. É comum termos várias APIs de pagamento no Brasil que precisam ser integradas ao sistema, e a documentação é essencial para isso.

Próximos Passos

No próximo vídeo, vamos adicionar a entidade de usuários, incluindo cadastro e autenticação. Mostraremos como restringir o uso de uma rota apenas para usuários autenticados. Também apresentaremos outra forma de documentar, além do JSON, utilizando YAML. Podemos criar arquivos grandes de documentação, mas também é possível documentar diretamente na rota com comentários, o que é uma abordagem interessante. Nos vemos no próximo vídeo!

Documentando APIs com Swagger UI - Autenticação e outras formas de documentação

Vamos adicionar uma nova rota à nossa documentação!

Estrutura Inicial do Sistema

Ao acessar os arquivos do sistema, podemos ver que temos um user.schema bem simples, onde o usuário é composto apenas por nome e senha. Realizamos a autenticação criptografando a senha. Atualmente, temos duas rotas no auth.routes: uma para registro e outra para login do usuário.

Criando a Rota de Registro (/auth/register)

De volta ao swagger.json, fara facilitar o trabalho, vamos minimizar tudo que é relacionado a books, como books/{title} e book/{id}. Dentro de paths, adicionaremos uma vírgula e inseriremos a nova rota, que será /auth/register.

Para registrar, utilizaremos o método post. No summary, podemos descrever o que essa rota faz, como "Create a user". Em seguida, adicionaremos uma nova tag tags e passamos um array com Auth.

"paths": {
    "/books": { ... },
    "/books/{title}": { ... },
    "/books/{id}": { ... },
    "/auth/register": {
        "post": {
            "summary": "Create a User",
            "tags": [
                "Auth"
            ]
        }

Após salvar, ao atualizar a tela da documentação, podemos ver que agora temos duas seções: uma para registro, que ainda está em desenvolvimento, e outra para livros. Vamos continuar configurando essa rota. É importante escrever parte por parte e conferir se tudo está correto.

Configurando o Corpo da Requisição (requestBody)

Na propriedade requestBody, definimos required como true, indicando que não pode ser deixado em branco. O content será application/json e passaremos um schema do tipo object. Não criamos um esquema específico para isso, pois será usado apenas para autenticação, demonstrando que nem tudo precisa de um esquema.

Definimos as properties com username do tipo string e password também do tipo string.

"paths": {
    "/books": { ... },
    "/books/{title}": { ... },
    "/books/{id}": { ... },
    "/auth/register": {
        "post": {
            "summary": "Create a User",
            "tags": [
                "Auth"
            ],
            "requestBody": {
                "required": true,
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "username": {
                                    "type": "string"
                                },
                                "password": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Definição das Respostas (responses)

Após o requestBody, adicionamos uma vírgula e cadastramos as respostas. Para o caso de 201, que indica criação, a descrição será "Usuário cadastrado com sucesso". Passamos novamente o content como application/json e definimos um schema com type object. Nas properties, incluímos uma message do tipo string.

"paths": {
    "/books": { ... },
    "/books/{title}": { ... },
    "/books/{id}": { ... },
    "/auth/register": {
        "post": {
            "summary": "Create a User",
            "tags": [
                "Auth"
            ],
            "requestBody": {
                "required": true,
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "username": {
                                    "type": "string"
                                },
                                "password": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "responses": {
                "201": {
                    "description": "Usuário cadastrado com sucesso",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "message": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Testando a Rota de Registro

Após salvar, ao atualizar a documentação, podemos expandir e ver username, password e a mensagem de resposta do tipo string. Vamos testar o cadastro com username como "Thiago Bussola" e senha "teste123" para facilitar:

{
    "username": "Thiago Bussola",
    "password": "string"
}

Ao executar, recebemos a mensagem "Usuário cadastrado com sucesso", conforme esperávamos. Vamos abrir nosso banco de dados, acessar a tabela de users e realizar uma busca. O usuário está presente e a senha está criptografada.

Criando a Rota de Login (/auth/login)

Vamos retornar ao nosso Visual Studio Code e continuar com a parte do login. Minimizamos o auth/register para não nos perder e criamos uma nova rota: /auth/login.

O login também será um post, com o summary Login User, e as tags Auth. O corpo da requisição será obrigatório, pois não podemos passar em branco, e o conteúdo será application/json. O esquema será do tipo object, com as propriedades username e password do tipo string .

"/auth/login": {
    "post": {
        "summary": "Login User",
        "tags": ["Auth"],
        "requestBody": {
            "required": true,
            "content": {
                "application/json": {
                    "schema": {
                        "type": "object",
                        "properties": {
                            "username": {
                                "type": "string"
                            },
                            "password": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        }
    }
}

Após o requesteBody, incluímos uma vírgula e implementamos o responses da seguinte forma:

"/auth/login": {
    "post": {
        "summary": "Login User",
        "tags": ["Auth"],
        "requestBody": {...},
        "responses": {
            "200": {
                "description": "Logado com sucesso",
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "token": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Como atividade, você deve implementar os retornos de 401 logo abaixo do retorno 200.

Testando a Rota de Login

Vamos testar o login para ver se o token está retornando corretamente. Na documentação, abrimos a tag Login User e clicamos em Try it out. Utilizaremos o seguinte JSON de exemplo:

{
    "username": "ThiagoBussola",
    "password": "Teste123"
}

Ao executar, ele retorna um token. Não vamos copiá-lo ainda, mas temos a parte de autorização, permitindo o uso de determinadas rotas. Vamos entender o funcionamento.

Restringindo Rotas com Autenticação

Podemos restringir a rota Get all books. No código do arquivo routes.ts, adicionamos o verifyToken, o middleware de autenticação que pode ser aplicado em quantas rotas quisermos.

routes.post("/books", bookController.create);
routes.get("/books", verifyToken, bookController.find);
routes.get("/books/:title", bookController.findByTitle);
routes.put("/books/:id", bookController.update);
routes.delete("/books/:id", bookController.delete);

Ao tentar usar a rota sem estar logado, recebemos a mensagem de que o token não foi provisionado. No entanto, o cadastro de um livro é permitido para qualquer pessoa. Apenas a busca é restrita a usuários autenticados.

Vamos autenticar novamente, copiar o token e utilizá-lo para autorizar. Após isso, a rota de busca funciona e retorna os livros.

Documentando com swagger-jsdoc

A próxima etapa é documentar de outra forma, usando comentários. Vamos apagar a parte escrita manualmente de auth/login e auth/register, salvar, e atualizar a documentação. As seções deletadas do código também desaparecem da documentação.

Em seguida, precisamos instalar a biblioteca swagger-jsdoc com npm install swagger-jsdoc.

npm install swagger-jsdoc

Para evitar conflitos, desabilitaremos a documentação atual. No arquivo app.ts, comentamos um treco do código de private middleware(), além da importação do swaggerJson. Em seguida, movemos o arquivo swagger.json para fora da pasta user, renomeando-o para swagger-old.json. A documentação ficará apenas com as rotas de autenticação.

Trechos de código com comentário:

// import swaggerJson from "./swagger.json";
private middleware(): void {
    this.express.use(express.json());
    // this.express.use(
    //   "/api-docs",
    //   swaggerUi.serve,
    //   swaggerUi.setup(swaggerJson)
    // );
}

Configurando swagger-jsdoc

Criamos um arquivo que será disponibilizado, usando swagger-jsdoc. Ele cria no formato de comentário, incluindo propriedades e exemplos do livro. A configuração é feita nas rotas auth/routes e routes, e o arquivo principal será swagger.ts.

Código omitido.

Feito isso, precisamos instalar os tipos com npm install @types/swagger-jsdoc --save-dev.

npm install @types/swagger-jsdoc --save-dev

No arquivo de API, app.ts, mudamos para atender ao novo Swagger. Importamos como swaggerSpec e configuramos o express para usar o novo endereço /docs. A configuração é semelhante, mas agora o setup é o swaggerSpec.

import swaggerSpec from "./swagger";
private middleware(): void {
    this.express.use(express.json());
    this.express.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
    // this.express.use(
    //   "/api-docs",
    //   swaggerUi.serve,
    //   swaggerUi.setup(swaggerJson)
    // );
}

Ao acessar /docs, vemos que não há rotas ainda. Vamos documentar rapidamente a rota de users em auth.routes.ts. Criamos uma rota para o registro, usando comentários multilinha. Usamos @swagger e colamos o restante para facilitar. As propriedades são as mesmas do JSON, mas agora sem abrir o objeto a cada vez.

Código omitido.

Vamos testar o registro com os seguintes dados:

{
    "username": "Matheus Augusto",
    "password": "Teste123"
}

Ao executar, vemos que o registro é criado com sucesso.

Para o login, seguimos o mesmo processo, documentando com comentários. No routes.ts, você deve passar a documentação para o formato de comentário em cima de cada rota.

Conclusão

Existem dois estilos de documentação: com comentários ou com arquivo JSON. A escolha depende de cada pessoa desenvolvedora, do time e da empresa. O importante é não faltar documentação. Escolha uma abordagem e documente suas rotas corretamente!

Nos vemos no próximo curso!

Sobre o curso Swagger: documentando APIs REST com OpenAPI

O curso Swagger: documentando APIs REST com OpenAPI possui 35 minutos de vídeos, em um total de 9 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas