Como documentar sua API com Swagger (OpenAPI) sem dor
← Voltar para Codeshort

Como documentar sua API com Swagger (OpenAPI) sem dor

Chega de documentação desatualizada e contratos mentirosos. Veja como configurar Swagger do jeito certo, do zero ao decorador.

DC
Dev Code Software
12 de junho de 2026·6 min de leitura

Por que a documentação morre antes da API

Se você já pegou um Postman collection desatualizado de um colega e passou meia hora descobrindo que o campo user_id agora se chama userId, você sabe do que estou falando. Documentação de API que vive separada do código é documentação que vai mentir pra você na pior hora possível — geralmente na véspera de uma entrega.

O problema não é falta de esforço. É arquitetura errada. Quando a doc fica num Notion, num README ou num Confluence que ninguém lembra de atualizar, ela envelhece sozinha. A solução é simples na teoria: documentação acoplada ao código, gerada automaticamente, versionada junto com a API.

É exatamente isso que o ecossistema OpenAPI/Swagger resolve.


O que é OpenAPI e o que é Swagger, afinal

A confusão entre os dois nomes é clássica. OpenAPI é a especificação — um padrão aberto (atualmente na versão 3.1) que define como descrever uma API REST em YAML ou JSON. Swagger era o nome original do projeto, que foi doado para a Linux Foundation e renomeado. Hoje, "Swagger" se refere às ferramentas: Swagger UI, Swagger Editor, Swagger Codegen.

Na prática: você escreve (ou gera) um arquivo OpenAPI, e o Swagger UI transforma isso numa interface interativa onde qualquer pessoa pode ver e testar os endpoints.

💡 Dica: A versão 3.0+ do OpenAPI é bem diferente da 2.0 (ainda chamada de "Swagger"). Se você está começando hoje, vá direto pra 3.x e economize dor de cabeça futura.


Estrutura mínima de um arquivo OpenAPI

Antes de integrar com qualquer framework, vale entender o que você está gerando. Um arquivo OpenAPI 3.0 tem esta estrutura básica:

openapi: 3.0.3
info:
  title: Minha API
  version: 1.0.0
  description: API para gerenciar usuários

servers:
  - url: https://api.exemplo.com/v1
  - url: http://localhost:3000/v1

paths:
  /users:
    get:
      summary: Lista todos os usuários
      tags:
        - Users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: Lista paginada de usuários
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'
        '401':
          description: Não autorizado

components:
  schemas:
    User:
      type: object
      required:
        - id
        - email
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
    UserList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        total:
          type: integer
        page:
          type: integer

O bloco components/schemas é onde você define seus modelos reutilizáveis. Usar $ref em vez de repetir a estrutura em todo endpoint é o que separa uma documentação sustentável de um pesadelo de manutenção.


Swagger no Node.js com Express

A abordagem mais direta com Express usa a lib swagger-jsdoc para gerar o spec a partir de comentários JSDoc, e swagger-ui-express para servir a interface.

npm install swagger-jsdoc swagger-ui-express
npm install -D @types/swagger-jsdoc @types/swagger-ui-express

Configure na entrada da aplicação:

// src/swagger.ts
import swaggerJsdoc from 'swagger-jsdoc';

const options = {
  definition: {
    openapi: '3.0.3',
    info: {
      title: 'Minha API',
      version: '1.0.0',
    },
    servers: [{ url: process.env.API_URL || 'http://localhost:3000' }],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
      },
    },
  },
  apis: ['./src/routes/**/*.ts'], // onde ficam seus comentários
};

export const swaggerSpec = swaggerJsdoc(options);
// src/app.ts
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import { swaggerSpec } from './swagger';

const app = express();

app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

E nos seus arquivos de rota:

// src/routes/users.ts

/**
 * @openapi
 * /users:
 *   get:
 *     tags:
 *       - Users
 *     summary: Lista usuários paginados
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - name: page
 *         in: query
 *         schema:
 *           type: integer
 *           default: 1
 *     responses:
 *       200:
 *         description: OK
 *       401:
 *         description: Token inválido ou ausente
 */
router.get('/users', authMiddleware, usersController.list);

⚠️ Atenção: Manter comentários JSDoc sincronizados com a lógica real do controller é a parte que exige disciplina. Se você muda o comportamento do endpoint e esquece de atualizar o comentário, voltamos ao problema inicial. Considere adicionar testes que validam o contrato OpenAPI.


Swagger no NestJS com decoradores

O NestJS tem integração nativa e bem mais ergonômica. A experiência aqui é bem diferente — você decora seus DTOs e controllers, e o framework gera o spec automaticamente.

npm install @nestjs/swagger swagger-ui-express
// main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Minha API')
    .setVersion('1.0')
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('docs', app, document);

  await app.listen(3000);
}
bootstrap();

Decorando o DTO:

// create-user.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @ApiProperty({ example: 'joao@exemplo.com' })
  @IsEmail()
  email: string;

  @ApiProperty({ example: 'João Silva' })
  @IsString()
  name: string;

  @ApiPropertyOptional({ example: 'admin' })
  role?: string;
}

E no controller:

@ApiTags('Users')
@ApiBearerAuth()
@Controller('users')
export class UsersController {
  @Post()
  @ApiOperation({ summary: 'Cria um novo usuário' })
  @ApiResponse({ status: 201, description: 'Usuário criado', type: UserEntity })
  @ApiResponse({ status: 409, description: 'Email já cadastrado' })
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

Essa abordagem ganha muito no NestJS porque os decoradores vivem no mesmo arquivo que a lógica. Ficou mais difícil de esquecer de atualizar.


Erros comuns que vão te fazer perder tempo

1. Não documentar os erros

Isso aconteceu num PR que revisei: o endpoint tinha só 200 documentado. Em produção, ele retornava 400, 401, 403 e 422 dependendo do contexto. O frontend ficou tratando tudo como erro genérico por semanas.

❌ Documentar só o caminho feliz:

responses:
  '200':
    description: OK

✓ Documentar os casos reais:

responses:
  '200':
    description: Usuário criado com sucesso
  '400':
    description: Dados inválidos no corpo da requisição
  '409':
    description: Email já está em uso
  '422':
    description: Entidade não processável — erro de validação detalhado

2. Repetir schemas em vez de usar $ref

Se você descreve o objeto User em cinco endpoints diferentes, na primeira vez que mudar um campo você vai esquecer de atualizar pelo menos um deles. Use $ref pra tudo que se repete.

3. Não usar example nos campos

Um schema sem exemplos força quem está lendo a adivinhar o formato esperado. Adicionar example: "2026-01-15T10:30:00Z" num campo de data evita uma série de e-mails desnecessários.

4. Não versionar a documentação junto com o código

Se o spec fica fora do repositório, ele vai ficar desatualizado. Commitar o openapi.yaml junto com o código garante que qualquer PR que muda um endpoint também muda a documentação — e o reviewer vai cobrar.


Versionamento e múltiplos ambientes

Uma situação comum: sua API tem v1 e v2 em produção ao mesmo tempo, mais o ambiente de staging. Como servir docs separadas?

// Gere um spec por versão
const v1Document = SwaggerModule.createDocument(app, configV1, {
  include: [UsersV1Module, AuthModule],
});

const v2Document = SwaggerModule.createDocument(app, configV2, {
  include: [UsersV2Module, AuthModule],
});

SwaggerModule.setup('docs/v1', app, v1Document);
SwaggerModule.setup('docs/v2', app, v2Document);

Para os ambientes, use a propriedade servers do spec para listar staging e produção. O Swagger UI deixa o usuário selecionar qual servidor usar diretamente na interface — o que elimina a necessidade de ter instâncias separadas de documentação.

const config = new DocumentBuilder()
  .setTitle('Minha API')
  .addServer('https://api.meusite.com', 'Produção')
  .addServer('https://staging-api.meusite.com', 'Staging')
  .addServer('http://localhost:3000', 'Local')
  .build();

FAQ

Preciso escrever o OpenAPI à mão ou posso gerar automaticamente?

Depende do stack. No NestJS você praticamente não escreve YAML à mão — os decoradores geram tudo. No Express com swagger-jsdoc, você escreve em comentários. Se você tem uma API legada sem nenhuma integração, ferramentas como swagger-autogen conseguem inferir endpoints existentes como ponto de partida, mas o resultado vai precisar de revisão manual.

Dá pra usar o OpenAPI spec pra gerar o client do frontend automaticamente?

Sim, e isso é uma das maiores vantagens. Ferramentas como openapi-typescript-codegen e orval leem seu spec e geram tipos TypeScript e até hooks React prontos. Seu frontend para de manter types na mão e passa a consumir o contrato da API diretamente.

Como protejo a rota /docs em produção?

O jeito mais simples é checar a variável de ambiente e só registrar a rota fora de produção. Se precisar liberar pra parceiros específicos, adicione um middleware de autenticação básica (HTTP Basic Auth) na frente do Swagger UI. Nunca exponha a documentação sem autenticação numa API que tem dados sensíveis.

O Swagger UI é pesado. Posso usar outra interface?

Sim. O spec OpenAPI é um padrão aberto — você não está preso ao Swagger UI. Alternativas como Redoc, Scalar e Stoplight Elements leem o mesmo arquivo e entregam interfaces mais limpas e modernas. O Scalar, em especial, tem ganhado bastante adoção por ter uma UI muito mais agradável que o Swagger UI padrão.

Como valido que minha implementação está de acordo com o spec?

Existe a biblioteca express-openapi-validator que faz exatamente isso: valida as requests e responses em runtime contra o seu spec. É especialmente útil em testes de integração — você garante que nenhum endpoint está divergindo do contrato documentado.


Próximos passos

Se você está começando agora, o caminho mais direto é:

  1. Escolha a abordagem certa pro seu stack — NestJS tem a melhor DX, Express com jsdoc é mais flexível, YAML puro dá mais controle.
  2. Commite o spec gerado no repositório — inclua no CI uma etapa que valida o arquivo OpenAPI com swagger-cli validate.
  3. Documente os erros desde o primeiro endpoint — não deixe pra depois. Depois nunca chega.
  4. Explore a geração de clients — se você tem frontend TypeScript, testar o orval vai te poupar horas de manutenção de tipos.
  5. Considere o Scalar como alternativa ao Swagger UI — a curva de adoção é zero, a interface é bem melhor.

Documentação de API não precisa ser uma tarefa separada que ninguém quer fazer. Quando ela vive junto com o código e é gerada automaticamente, ela simplesmente acontece — e para de ser o arquivo que todo mundo abre desconfiado de que está desatualizado.