Imagem contendo um relógio de parede circular com ponteiros dourados em um fundo branco, em sobreposição um ramo de folhas verdes desfocadas.
Photo by @surjasendas on Unsplash

Em se tratando de aplicações back-end, é essencial pensarmos em expor uma documentação, pois ela será fundamental para um melhor entendimento de como os endpoints funcionam e o que podem ou não fazer.

Não iremos focar em detalhes de implementação, por isso vamos partir do pressuposto que sua aplicação está minimamente configurada e possui pelo menos uma rota de API exposta.

Especificação

O primeiro ponto que precisamos levar em consideração é sobre a definição de regras e comportamentos de determinado endpoint, isso diz respeito à:

  1. Verbo HTTP suportado;
  2. Parâmetros de entrada;
  3. Modelos de resposta de sucesso e/ou erro;
  4. Modo de autenticação;
  5. Permissões quando disponível.

Bom, você pode estar se perguntando: "Ok, precisamos levantar várias definições para uma API, como faremos isso na prática?" (Se não se perguntou isso ainda, essa é a hora...), a resposta é: Open API!

O que é OpenAPI?

OpenAPI Specification (OAS) é um conjunto de conceitos e padrões para definir a estrutura e comportamento de APIs no protocolo HTTP, a ideia central é termos uma maneira agnóstica para essas regras que geralmente são definidas em um arquivo YAML ou JSON.

Principais pontos a se destacar ao utilizar as especificações da OAS:

  • Facilidade de identificação de serviços e contratos das APIs por meio da padronização.
  • Possibilidade de gerar código e testes com base nas definições do arquivo.
  • Automação de infraestrutura.

Utilizando em nosso código

Cada linguagem e framework terá sua maneira de implementar as especificações da OpenAPI, mas no geral todas irão seguir a mesma linha, ao criar um arquivo com as regras de todos os recursos expostos pela API.

Eis aqui um exemplo:

openapi: 3.0.0
info:
  title: Sample API
  description: A sample API to demonstrate OpenAPI specification
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
    description: Main production server
tags:
  - name: Users
    description: Operations related to users
  - name: Products
    description: Operations related to products
paths:
  /users:
    get:
      summary: Get all users
      tags:
        - Users
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
        '500':
          description: Internal server error
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          example: 12345
        name:
          type: string
          example: John Doe
        email:
          type: string
          example: john.doe@example.com
        createdAt:
          type: string
          format: date-time
          example: 2023-10-01T12:00:00Z

No exemplo acima, existe apenas um endpoint disponível: Get /users, você poderia passar horas escrevendo cada um, mas existem maneiras mais rápidas e eficientes de se fazer isso.

Automatizando o processo

Existem muitas maneiras de automatizar o processo de geração do arquivo com as especificações, vamos usar aqui uma estratégia que particularmente considero eficiente e ao mesmo tempo nos dá controle e transparência no código.

Usaremos como exemplo uma aplicação em NodeJS e duas bibliotecas para lidar com o processo de automação, sendo elas:

O processo de configuração é bem simples, primeiro precisamos criar um arquivo na raiz do projeto, dei o nome de: swagger.ts:

// swagger.ts

import swaggerJSDoc from 'swagger-jsdoc'

const swaggerDefinition = {
  openapi: '3.0.0',
  info: {
    title: 'Gen AI API',
    version: '1.0.0',
    description:
      'This is an Express service that provides authorization functionality and includes gen-AI features using RAG, Redis, Postgres, and Langchain.',
  },
  servers: [
    {
      url: 'http:localhost:3000',
    },
  ],
  tags: [
    {
      name: 'Auth',
      description: 'Endpoints related to authentication',
    },
    {
      name: 'Users',
      description: 'Endpoints related to users management',
    },
    {
      name: 'AI',
      description: 'Endpoints related to gen-AI features',
    },
  ],
  components: {
    securitySchemes: {
      bearerAuth: {
        type: 'http',
        scheme: 'bearer',
        bearerFormat: 'JWT',
      },
    },
  },
}

const options = {
  swaggerDefinition,
  apis: ['./src/modules/**/application/routes/*.ts'],
}

const swaggerSpec = swaggerJSDoc(options)

export default swaggerSpec

No código acima definimos alguns metadados para a descrição da API no geral, vale notar o campo: tags, esse array de objetos permite adicionar seções para agrupar os endpoints por tipos.

Caso queira entender melhor os valores permitidos para o arquivo de especificação, visite a documentação oficial.

Agora em cada rota da aplicação iremos adicionar um comentário com a especificação dessa rota em específico. Isso é importante, pois a aplicação irá fazer a leitura com base nos arquivos de rotas informado no campo apis demonstrado no arquivo swagger.ts no exemplo anterior.

// route.ts

import express from 'express'

const router = express.Router()

/**
 * @swagger
 * /status:
 *   get:
 *     summary: Returns a message to validade if API server is running
 *     responses:
 *       200:
 *         description: A successful response
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 message:
 *                   type: string
 *                   example: Systems up and running!
 */
router.get('/', (_, res) => {
  res.status(200).send({ message: 'Systems up and running!' })
})

export default router

Ao adicionar a anotação @swagger no início do comentário, estamos indicando que esse bloco deverá ser utilizado para a inclusão dessa rota na documentação, que ao final será mesclado com o restante do conteúdo.

Agora iremos expor um endpoint para visualizar a documentação, para isso, vá até o arquivo app.ts (ou correlato) da sua aplicação e adicione:

import express from 'express'
import swaggerUi from 'swagger-ui-express'

const app = express()
const port = process.env['PORT'] || config.port

...
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
...
app.listen(port, () => {
	console.log(`Server listening to http://localhost:${port}`)
})

Simples assim, ao executar a aplicação a documentação estará disponível no endpoint /docs.

Extra: Automatizando a automação usando agentes de IA

Até aqui ok, mas temos outro ponto, ainda precisamos de um pouco mais de esforço manual para a escrita dos comentários de cada endpoint. Graças aos modelos de AI generativa existente, podemos simplificar ainda mais esse processo, e solicitar que a AI gere o comentário no formato que precisamos.

Confira abaixo o prompt:

Generate the JSDocs comments using OpenAPI for the following API specs:

"""
endpoint name: /users/{id}
path parameter type: string
method: PUT
request body: {
  "name": "John Doe",
	"email": "john.doe@email.com",
}
summary: Update a user by given id and return it
200_response: {
	"success": true,
	"data": [
    {
      "id": "367b2539-bef4-412b-b94d-c9d2178dcdaa",
      "name": "John Doe",
      "email": "john.doe@gmail.com",
      "createdAt": "2024-09-30T21:04:18.656Z"
    }
  ]
}
400_response: {
  "success": false,
  "error": {
    "message": [
      "Invalid email address",
			"name field is required"
    ]
  }
}
headers: {
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
"""

More context: I'm using `swagger-ui-express` and `swagger-jsdoc` libraries to setup the Swagger documentation.
Give me only the response, without explanations.

Aqui estamos adicionando todas as informações necessárias para esse endpoint e também os exemplos de resposta, além de informar quais bibliotecas estaremos utilizando para a geração da documentação, essas informações adicionais são importantes porque dão mais contexto para o modelo e com isso recebemos respostas mais precisas.

Confira abaixo a resposta do agente para esse prompt:

/**
 * @swagger
 * /users/{id}:
 *   put:
 *     summary: Update a user by given id and return it
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name:
 *                 type: string
 *                 example: John Doe
 *               email:
 *                 type: string
 *                 example: john.doe@email.com
 *     responses:
 *       200:
 *         description: A successful response
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: string
 *                       example: 367b2539-bef4-412b-b94d-c9d2178dcdaa
 *                     name:
 *                       type: string
 *                       example: John Doe
 *                     email:
 *                       type: string
 *                       example: john.doe@gmail.com
 *                     createdAt:
 *                       type: string
 *                       format: date-time
 *                       example: 2024-09-30T21:04:18.656Z
 *       400:
 *         description: Invalid request
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: false
 *                 error:
 *                   type: object
 *                   properties:
 *                     message:
 *                       type: array
 *                       items:
 *                         type: string
 *                       example: ["Invalid email address", "name field is required"]
 *     security:
 *       - bearerAuth: []
 */

Agora só precisamos conferir e fazer os ajustes se necessários no comentário gerado e adicionar logo acima da definição do endpoint da API.

Finalizando

Neste artigo aprendemos um pouco mais sobre o padrão OpenAPI e como podemos automatizar o processo de documentar uma API, além de fazer o uso de agentes de AI para facilitar ainda mais o processo.

Espero que esse conteúdo tenha trazido algum conhecimento novo pra você ou até mesmo insights para novas ideias, obrigado pelo seu tempo de leitura e te vejo na próxima! :)

Confira abaixo como ficou a geração da documentação do meu projeto, e caso queira entender melhor e conferir a implementação -- É só seguir esse link.

Imagem da documentação gerada pelo Swagger UI