En esta página

Swagger y documentación de API

12 min lectura TextoCap. 5 — Producción

¿Por qué documentar tu API con Swagger?

Una API sin documentación es una API que nadie puede usar eficientemente. Swagger (especificación OpenAPI) proporciona:

  1. Documentación interactiva: Los desarrolladores pueden explorar y probar los endpoints directamente en el navegador
  2. Generación de clientes: A partir de la especificación se pueden generar clientes SDK para múltiples lenguajes
  3. Contrato de API: La especificación actúa como contrato entre el equipo de backend y frontend
  4. Testing: Permite probar endpoints manualmente durante el desarrollo

Instalación

npm install @nestjs/swagger swagger-ui-express

Si usas Fastify en lugar de Express:

npm install @nestjs/swagger @fastify/static

Documentando entidades de TypeORM

Para que las entidades aparezcan correctamente como modelos en Swagger, úsalas con @ApiProperty:

// productos/entities/producto.entity.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('productos')
export class Producto {
  @ApiProperty({ description: 'ID único del producto', example: 'uuid-aqui' })
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ApiProperty({ example: 'Laptop Gaming Pro' })
  @Column()
  nombre: string;

  @ApiPropertyOptional({ example: 'Descripción opcional' })
  @Column({ nullable: true })
  descripcion: string | null;

  @ApiProperty({ example: 1299.99 })
  @Column({ type: 'decimal' })
  precio: number;

  @ApiProperty({ example: '2024-01-15T10:30:00.000Z' })
  @CreateDateColumn()
  creadoEn: Date;
}

Registrando modelos extra en la especificación

Para que los modelos de respuesta complejos aparezcan en el esquema de Swagger, debes extraModels:

const documento = SwaggerModule.createDocument(app, config, {
  extraModels: [ResultadoPaginado, ErrorResponse, Producto, Usuario],
});

Documentando la autenticación

// auth/auth.controller.ts — con decoradores de Swagger
import { ApiTags, ApiOperation, ApiBody, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';

class LoginResponseDto {
  @ApiProperty({ description: 'Token JWT de acceso', example: 'eyJhbGci...' })
  accessToken: string;
}

@ApiTags('Auth')
@Controller('auth')
export class AuthController {

  @Post('login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Iniciar sesión',
    description: 'Autentica al usuario y retorna un token JWT de acceso',
  })
  @ApiBody({
    schema: {
      type: 'object',
      required: ['email', 'password'],
      properties: {
        email: { type: 'string', format: 'email', example: '[email protected]' },
        password: { type: 'string', minLength: 8, example: 'MiPassword1!' },
      },
    },
  })
  @ApiOkResponse({ type: LoginResponseDto, description: 'Login exitoso' })
  @ApiUnauthorizedException({ description: 'Credenciales incorrectas' })
  login(@Body() loginDto: LoginDto) {
    return this.authService.login(loginDto);
  }
}

Grouping con tags y versiones

// Organiza los endpoints en grupos lógicos con tags
@ApiTags('Administración — Usuarios')
@Controller('admin/usuarios')
export class AdminUsuariosController {}

@ApiTags('Administración — Configuración')
@Controller('admin/config')
export class AdminConfigController {}

Exportar la especificación OpenAPI como JSON

Para CI/CD o para generar clientes, puedes guardar la especificación como archivo:

// scripts/generate-swagger.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from '../src/app.module';
import * as fs from 'fs';

async function generateSwagger() {
  const app = await NestFactory.create(AppModule, { logger: false });

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

  const documento = SwaggerModule.createDocument(app, config);

  fs.writeFileSync(
    './openapi.json',
    JSON.stringify(documento, null, 2),
    'utf-8'
  );

  await app.close();
  console.log('Especificación OpenAPI generada en openapi.json');
}

generateSwagger();

Deshabilitar Swagger en producción

Es una buena práctica no exponer la documentación en producción:

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

  if (process.env['NODE_ENV'] !== 'production') {
    const config = new DocumentBuilder()
      .setTitle('Mi API')
      .setVersion('1.0')
      .addBearerAuth()
      .build();

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

  await app.listen(3000);
}

Documentando modelos de respuesta paginada

// common/dto/paginated-response.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Type } from '@nestjs/common';

export function PaginatedResponseDto<T>(ItemClass: Type<T>) {
  class PaginatedResponseDtoClass {
    @ApiProperty({ isArray: true, type: () => ItemClass })
    datos: T[];

    @ApiProperty({ example: 100 })
    total: number;

    @ApiProperty({ example: 1 })
    pagina: number;

    @ApiProperty({ example: 10 })
    totalPaginas: number;

    @ApiProperty({ example: true })
    tieneSiguiente: boolean;
  }
  return PaginatedResponseDtoClass;
}

// Uso en el controlador
class ProductosPaginadosResponse extends PaginatedResponseDto(Producto) {}

@ApiOkResponse({ type: ProductosPaginadosResponse })
findAll() {}

Con Swagger configurado correctamente, tu API está completamente documentada y lista para ser consumida por cualquier equipo. En la próxima lección aprenderemos a escribir tests robustos para garantizar que toda esta funcionalidad funciona correctamente.

Usa ApiProperty en todas las clases de respuesta
Para que Swagger genere modelos de respuesta precisos, decora con `@ApiProperty` no solo los DTOs de entrada sino también las entidades y clases de respuesta. Si las entidades de TypeORM no deben exponerse directamente, crea clases de respuesta separadas (Response DTOs) con `@ApiProperty` y usa el decorador `@Exclude()` de class-transformer en las propiedades sensibles.
Genera el cliente TypeScript desde la especificación OpenAPI
La especificación JSON que genera Swagger (`/docs-json`) puede usarse con herramientas como `openapi-typescript-codegen` o `@openapitools/openapi-generator-cli` para generar automáticamente un cliente TypeScript tipado para tu frontend. Esto elimina la necesidad de mantener tipos de API manualmente en el frontend.