OpenAPI/Swagger em Node.js: documentação automática

OpenAPI (Swagger) é o padrão para documentar APIs REST, descreve todos os endpoints, parâmetros, schemas de request/response e autenticação em um arquivo JSON/YAML. Com as bibliotecas certas, a documentação é gerada automaticamente do código e fica sempre sincronizada, eliminando docs desatualizados.

Configurar swagger-jsdoc e swagger-ui-express

Gerar documentação OpenAPI a partir de comentários JSDoc:

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

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

const swaggerOptions: swaggerJsdoc.Options = {
  definition: {
    openapi: '3.1.0',
    info: {
      title: 'Minha API',
      version: '2.0.0',
      description: 'API de gerenciamento de pedidos',
      contact: {
        name: 'Suporte',
        email: 'api@seudominio.com.br',
      },
    },
    servers: [
      { url: 'https://api.seudominio.com.br', description: 'Produção' },
      { url: 'http://localhost:3000', description: 'Desenvolvimento' },
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
        apiKey: {
          type: 'apiKey',
          in: 'header',
          name: 'X-API-Key',
        },
      },
    },
    security: [{ bearerAuth: [] }],  // segurança padrão para todos os endpoints
  },
  apis: ['./src/routes/**/*.ts', './src/schemas/*.ts'],  // arquivos com anotações
}

const swaggerSpec = swaggerJsdoc(swaggerOptions)

// Servir documentação interativa:
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
  customSiteTitle: 'Minha API: Docs',
  swaggerOptions: {
    persistAuthorization: true,  // manter token entre reloads
  },
}))

// Expor o spec JSON para ferramentas:
app.get('/docs/openapi.json', (req, res) => res.json(swaggerSpec))

Documentar endpoints com JSDoc

Anotar rotas com comentários OpenAPI:

typescript
// src/routes/v2/pedidos.ts

/**
 * @swagger
 * /api/v2/pedidos:
 *   get:
 *     summary: Listar pedidos do usuário autenticado
 *     tags: [Pedidos]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: status
 *         schema:
 *           type: string
 *           enum: [pendente, aprovado, cancelado]
 *         description: Filtrar por status
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           default: 20
 *           maximum: 100
 *     responses:
 *       200:
 *         description: Lista de pedidos
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 pedidos:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/Pedido'
 *                 total:
 *                   type: integer
 *       401:
 *         $ref: '#/components/responses/NaoAutorizado'
 */
router.get('/', autenticar, listarPedidos)

/**
 * @swagger
 * /api/v2/pedidos:
 *   post:
 *     summary: Criar novo pedido
 *     tags: [Pedidos]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/CriarPedidoInput'
 *     responses:
 *       201:
 *         description: Pedido criado
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Pedido'
 *       400:
 *         $ref: '#/components/responses/EntradaInvalida'
 */
router.post('/', autenticar, criarPedido)

Definir schemas com Zod e gerar OpenAPI

Usar zod-to-openapi para gerar specs do Zod automaticamente:

typescript
// npm install @asteasolutions/zod-to-openapi zod

import { z } from 'zod'
import { extendZodWithOpenApi, OpenApiGeneratorV31, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'

extendZodWithOpenApi(z)

const registry = new OpenAPIRegistry()

// Definir schema com Zod (com anotações OpenAPI):
const PedidoSchema = registry.register(
  'Pedido',
  z.object({
    id: z.string().uuid().openapi({ description: 'ID único do pedido' }),
    status: z.enum(['pendente', 'aprovado', 'cancelado']).openapi({ example: 'pendente' }),
    valor_total: z.number().positive().openapi({ description: 'Valor total em reais', example: 150.00 }),
    moeda: z.string().length(3).default('BRL').openapi({ example: 'BRL' }),
    criado_em: z.string().datetime().openapi({ description: 'Data de criação ISO 8601' }),
  }).openapi('Pedido')
)

const CriarPedidoInputSchema = registry.register(
  'CriarPedidoInput',
  z.object({
    itens: z.array(z.object({
      produto_id: z.string().uuid(),
      quantidade: z.number().int().min(1),
    })).min(1).openapi({ description: 'Lista de itens do pedido' }),
    observacao: z.string().max(500).optional(),
  }).openapi('CriarPedidoInput')
)

// Registrar endpoint:
registry.registerPath({
  method: 'get',
  path: '/api/v2/pedidos',
  summary: 'Listar pedidos',
  tags: ['Pedidos'],
  security: [{ bearerAuth: [] }],
  responses: {
    200: {
      description: 'Lista de pedidos',
      content: {
        'application/json': {
          schema: z.object({ pedidos: z.array(PedidoSchema) }),
        },
      },
    },
  },
})

// Gerar spec:
const generator = new OpenApiGeneratorV31(registry.definitions)
const spec = generator.generateDocument({
  openapi: '3.1.0',
  info: { title: 'Minha API', version: '2.0.0' },
})

Validação automática de request com spec OpenAPI

Validar inputs contra a spec OpenAPI usando express-openapi-validator:

typescript
// npm install express-openapi-validator

import OpenApiValidator from 'express-openapi-validator'

// Configurar validador antes das rotas:
app.use(
  OpenApiValidator.middleware({
    apiSpec: './openapi.json',        // ou o objeto spec gerado
    validateRequests: true,           // validar body, query, params
    validateResponses: false,         // opcional: validar respostas (desenvolvimento)
    ignorePaths: /\/docs/,            // não validar a rota de docs
  })
)

// Handler de erros de validação:
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
  if ((err as { status?: number }).status === 400) {
    return res.status(400).json({
      error: 'Entrada inválida',
      detalhes: (err as { errors?: unknown[] }).errors,
    })
  }
  next(err)
})

// Agora o middleware valida automaticamente:
// POST /api/v2/pedidos sem "itens" → 400 com detalhes do erro
// GET /api/v2/pedidos?status=invalido → 400 (não está no enum)

// Tip: testar a spec com ferramentas CLI:
// npx openapi-validator openapi.json
// npx @stoplight/spectral-cli lint openapi.json

Gerar clients TypeScript automaticamente

Usar openapi-ts para gerar clients type-safe a partir da spec:

bash
# Gerar client TypeScript para o frontend a partir da spec:
# npm install -D @hey-api/openapi-ts

# package.json:
# "generate:client": "openapi-ts -i http://localhost:3000/docs/openapi.json -o src/api-client"

npx @hey-api/openapi-ts \
  --input http://localhost:3000/docs/openapi.json \
  --output src/api-client \
  --client fetch

# Resultado: src/api-client/
#   types.gen.ts     → tipos TypeScript de todos os schemas
#   services.gen.ts  → funções de chamada type-safe
#   index.ts

# Usar no frontend React:
# import { PedidosService } from '@/api-client'
# const pedidos = await PedidosService.listarPedidos({ status: 'pendente' })
# pedidos.data.pedidos[0].valor_total  ← TypeScript sabe o tipo!

# Alternativas:
# openapi-generator-cli: gera para múltiplas linguagens (Java, Python, Go...)
# swagger-typescript-api: templates customizáveis
# orval: geração com React Query hooks integrados

# No CI/CD: garantir que spec e client estão sincronizados:
# 1. npm run generate:client
# 2. git diff --exit-code src/api-client/ || (echo "Client desatualizado!" && exit 1)

Não quer configurar manualmente?

Implante o VPS para APIs em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

swagger-jsdoc: documenta com comentários JSDoc próximos às rotas, mais verboso, mas mantém a doc próxima do código. zod-to-openapi: gera spec diretamente dos schemas Zod, elimina duplicação (o mesmo schema valida o request E documenta). Para projetos que já usam Zod para validação: zod-to-openapi evita duplicação. Para projetos sem Zod ou com equipes menos familiarizadas com schema-first: swagger-jsdoc é mais acessível.

OpenAPI 3.1 é a versão atual (2021), totalmente compatível com JSON Schema 2020-12, você pode usar qualquer schema JSON Schema diretamente. 3.0 ainda é amplamente suportado por ferramentas. Diferenças práticas: 3.1 suporta nullable corretamente (via oneOf/anyOf com null), webhooks, e todas as keywords JSON Schema. Para novos projetos: comece com 3.1. Para migrar 3.0 para 3.1: principalmente substituir nullable: true por type: ["string", "null"].

Depende do tipo de API. APIs públicas/de terceiros: sim, documentação em produção é essencial para adoção. APIs internas: considere restringir por IP, autenticação básica, ou servir apenas em staging. Minimize o risco: não exponha detalhes internos desnecessários na spec, não inclua endpoints de admin sem autenticação. O spec JSON (/openapi.json) pode ser protegido mesmo com a UI pública.

Para código-first (anotações/Zod): a spec é gerada do código, por definição sempre sincronizada. Para spec-first (escrever o YAML/JSON manualmente): use express-openapi-validator para validar que o código implementa o contrato, e adicione no CI um passo que compara a spec atual com a esperada. Code review obrigatório para mudanças na spec. A abordagem código-first com Zod é a mais sustentável para APIs em evolução.

Não diretamente, mas complementa. A spec define o contrato; testes verificam o comportamento. Ferramentas como Schemathesis (property-based testing) geram casos de teste automaticamente a partir da spec OpenAPI, testam campos obrigatórios, tipos de dados, enums. Combine: OpenAPI para contrato e descoberta de erros de tipo, testes de integração com banco real para comportamento de negócio.