22 API-Dokumentation

API-Dokumentation ist essentiell für die erfolgreiche Adoption und Wartung von RESTful APIs. NestJS bietet mit seiner integrierten OpenAPI/Swagger-Unterstützung eine mächtige Plattform für die automatische Generierung umfassender, interaktiver API-Dokumentationen. Dieses Kapitel zeigt, wie Sie professionelle API-Dokumentationen erstellen, die sowohl für Entwickler als auch für Endnutzer wertvoll sind.

22.1 OpenAPI/Swagger Integration

Die Integration von OpenAPI (ehemals Swagger) in NestJS ermöglicht es, aus TypeScript-Code automatisch umfassende API-Dokumentationen zu generieren.

22.1.1 Grundlegende Swagger Setup

import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { INestApplication } from '@nestjs/common';

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

  // Swagger-Konfiguration
  const config = new DocumentBuilder()
    .setTitle('NestJS API Documentation')
    .setDescription('Comprehensive API documentation for our NestJS application')
    .setVersion('1.0.0')
    .setContact(
      'API Support Team',
      'https://example.com/support',
      'api-support@example.com'
    )
    .setLicense(
      'MIT License',
      'https://opensource.org/licenses/MIT'
    )
    .setTermsOfService('https://example.com/terms')
    .addServer('https://api.example.com/v1', 'Production Server')
    .addServer('https://staging-api.example.com/v1', 'Staging Server')
    .addServer('http://localhost:3000', 'Development Server')
    .addBearerAuth(
      {
        type: 'http',
        scheme: 'bearer',
        bearerFormat: 'JWT',
        name: 'JWT',
        description: 'Enter JWT token',
        in: 'header',
      },
      'JWT-auth' // This name here is important for matching up with @ApiBearerAuth() in controllers
    )
    .addApiKey(
      {
        type: 'apiKey',
        name: 'X-API-Key',
        in: 'header',
        description: 'API Key for authentication',
      },
      'api-key'
    )
    .build();

  const document = SwaggerModule.createDocument(app, config, {
    operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
    deepScanRoutes: true,
  });

  // Swagger UI Setup
  SwaggerModule.setup('api/docs', app, document, {
    customSiteTitle: 'API Documentation',
    customfavIcon: '/favicon.ico',
    customCss: '.swagger-ui .topbar { display: none }',
    swaggerOptions: {
      persistAuthorization: true,
      displayRequestDuration: true,
      filter: true,
      showExtensions: true,
      showCommonExtensions: true,
      docExpansion: 'none',
      defaultModelsExpandDepth: 3,
      defaultModelExpandDepth: 3,
    },
  });

  // JSON-Dokument als Endpoint bereitstellen
  SwaggerModule.setup('api/docs-json', app, document, {
    jsonDocumentUrl: 'api/docs-json',
    yamlDocumentUrl: 'api/docs-yaml',
  });

  await app.listen(3000);
  console.log('API Documentation available at: http://localhost:3000/api/docs');
}

bootstrap();

22.1.2 Erweiterte Swagger-Konfiguration

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, OpenAPIObject } from '@nestjs/swagger';

@Injectable()
export class SwaggerConfigService {
  constructor(private configService: ConfigService) {}

  createSwaggerConfig(): Omit<OpenAPIObject, 'paths'> {
    const version = this.configService.get('API_VERSION', '1.0.0');
    const environment = this.configService.get('NODE_ENV', 'development');

    const builder = new DocumentBuilder()
      .setTitle('Enterprise API')
      .setDescription(this.getApiDescription())
      .setVersion(version);

    // Environment-spezifische Konfiguration
    if (environment === 'production') {
      builder
        .addServer(this.configService.get('API_BASE_URL'), 'Production API')
        .addBearerAuth()
        .addApiKey({ type: 'apiKey', name: 'X-API-Key', in: 'header' }, 'api-key');
    } else {
      builder
        .addServer('http://localhost:3000', 'Development Server')
        .addServer('https://staging.api.com', 'Staging Server');
    }

    // Tags für bessere Organisation
    this.addApiTags(builder);

    // Externe Dokumentation
    builder.setExternalDoc(
      'Additional Documentation',
      'https://docs.example.com/api'
    );

    return builder.build();
  }

  private getApiDescription(): string {
    return `
## Enterprise API Documentation

This comprehensive API provides access to all core business functions including:

- **User Management**: Complete CRUD operations for user accounts
- **Authentication**: JWT-based authentication with refresh tokens
- **Product Catalog**: Advanced product search and filtering
- **Order Processing**: End-to-end order management
- **Analytics**: Real-time business intelligence data

### Rate Limiting
- **Authenticated Users**: 1000 requests per hour
- **Public Endpoints**: 100 requests per hour

### Error Handling
All endpoints return standardized error responses with proper HTTP status codes.

### Changelog
- **v1.2.0**: Added advanced filtering options
- **v1.1.0**: Introduced webhook support
- **v1.0.0**: Initial API release
    `;
  }

  private addApiTags(builder: DocumentBuilder): void {
    const tags = [
      { name: 'Authentication', description: 'User authentication and authorization' },
      { name: 'Users', description: 'User management operations' },
      { name: 'Products', description: 'Product catalog and inventory' },
      { name: 'Orders', description: 'Order processing and fulfillment' },
      { name: 'Analytics', description: 'Business intelligence and reporting' },
      { name: 'Webhooks', description: 'Event-driven integrations' },
      { name: 'Health', description: 'API health and monitoring' },
    ];

    tags.forEach(tag => builder.addTag(tag.name, tag.description));
  }

  // Custom OpenAPI Transformations
  transformDocument(document: OpenAPIObject): OpenAPIObject {
    // Security Schemes hinzufügen
    document.components = {
      ...document.components,
      securitySchemes: {
        ...document.components?.securitySchemes,
        BearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
        ApiKeyAuth: {
          type: 'apiKey',
          in: 'header',
          name: 'X-API-Key',
        },
      },
      responses: {
        ...document.components?.responses,
        UnauthorizedError: {
          description: 'Authentication information is missing or invalid',
          content: {
            'application/json': {
              schema: {
                type: 'object',
                properties: {
                  statusCode: { type: 'number', example: 401 },
                  message: { type: 'string', example: 'Unauthorized' },
                  timestamp: { type: 'string', format: 'date-time' },
                },
              },
            },
          },
        },
        ValidationError: {
          description: 'Validation failed for the request',
          content: {
            'application/json': {
              schema: {
                type: 'object',
                properties: {
                  statusCode: { type: 'number', example: 400 },
                  message: { 
                    type: 'array',
                    items: { type: 'string' },
                    example: ['name should not be empty', 'email must be a valid email']
                  },
                  error: { type: 'string', example: 'Bad Request' },
                },
              },
            },
          },
        },
      },
    };

    // Global Security Requirements
    document.security = [
      { BearerAuth: [] },
      { ApiKeyAuth: [] },
    ];

    return document;
  }
}

// Usage in Main
export async function setupSwagger(app: INestApplication): Promise<void> {
  const swaggerConfig = app.get(SwaggerConfigService);
  const config = swaggerConfig.createSwaggerConfig();
  
  let document = SwaggerModule.createDocument(app, config);
  document = swaggerConfig.transformDocument(document);

  SwaggerModule.setup('api/docs', app, document, {
    customSiteTitle: 'Enterprise API Documentation',
    customCss: `
      .swagger-ui .topbar { background-color: #1976d2; }
      .swagger-ui .topbar-wrapper .link { color: white; }
      .swagger-ui .info .title { color: #1976d2; }
    `,
    swaggerOptions: {
      persistAuthorization: true,
      displayRequestDuration: true,
      tryItOutEnabled: true,
      filter: true,
      showExtensions: true,
    },
  });
}

22.1.3 Multi-Document Setup für verschiedene Audiences

import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

export class MultiDocumentSwagger {
  static setup(app: INestApplication): void {
    // Public API Documentation
    this.setupPublicDocs(app);
    
    // Internal API Documentation
    this.setupInternalDocs(app);
    
    // Admin API Documentation
    this.setupAdminDocs(app);
  }

  private static setupPublicDocs(app: INestApplication): void {
    const config = new DocumentBuilder()
      .setTitle('Public API')
      .setDescription('Customer-facing API endpoints')
      .setVersion('1.0.0')
      .addBearerAuth()
      .build();

    const document = SwaggerModule.createDocument(app, config, {
      include: [
        // Nur öffentliche Module einschließen
        PublicUserModule,
        ProductCatalogModule,
        OrderModule,
      ],
    });

    SwaggerModule.setup('api/public-docs', app, document, {
      customSiteTitle: 'Public API Documentation',
    });
  }

  private static setupInternalDocs(app: INestApplication): void {
    const config = new DocumentBuilder()
      .setTitle('Internal API')
      .setDescription('Internal microservice communication')
      .setVersion('1.0.0')
      .addApiKey({ type: 'apiKey', name: 'X-Service-Key', in: 'header' })
      .build();

    const document = SwaggerModule.createDocument(app, config, {
      include: [
        InternalUserModule,
        AnalyticsModule,
        ReportingModule,
      ],
    });

    SwaggerModule.setup('api/internal-docs', app, document, {
      customSiteTitle: 'Internal API Documentation',
    });
  }

  private static setupAdminDocs(app: INestApplication): void {
    const config = new DocumentBuilder()
      .setTitle('Admin API')
      .setDescription('Administrative operations')
      .setVersion('1.0.0')
      .addBearerAuth()
      .build();

    const document = SwaggerModule.createDocument(app, config, {
      include: [
        AdminModule,
        SystemConfigModule,
        UserManagementModule,
      ],
    });

    SwaggerModule.setup('api/admin-docs', app, document, {
      customSiteTitle: 'Admin API Documentation',
    });
  }
}

22.2 Automatische Schema-Generierung

TypeScript-Typen und Decorators ermöglichen die automatische Generierung präziser OpenAPI-Schemas.

22.2.1 DTO-basierte Schema-Generierung

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { 
  IsEmail, 
  IsString, 
  IsNumber, 
  IsOptional, 
  IsEnum, 
  IsArray, 
  ValidateNested,
  IsDateString,
  Min,
  Max,
  Length 
} from 'class-validator';
import { Type, Transform } from 'class-transformer';

export enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  MODERATOR = 'moderator',
}

export enum UserStatus {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  SUSPENDED = 'suspended',
}

// Base DTO mit gemeinsamen Properties
export class BaseUserDto {
  @ApiProperty({
    description: 'User email address',
    example: 'user@example.com',
    format: 'email',
  })
  @IsEmail()
  email: string;

  @ApiProperty({
    description: 'User full name',
    example: 'John Doe',
    minLength: 2,
    maxLength: 100,
  })
  @IsString()
  @Length(2, 100)
  name: string;

  @ApiPropertyOptional({
    description: 'User role in the system',
    enum: UserRole,
    default: UserRole.USER,
    example: UserRole.USER,
  })
  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole = UserRole.USER;
}

// Create User DTO
export class CreateUserDto extends BaseUserDto {
  @ApiProperty({
    description: 'User password (minimum 8 characters)',
    example: 'SecurePassword123!',
    minLength: 8,
    format: 'password',
  })
  @IsString()
  @Length(8, 128)
  password: string;

  @ApiPropertyOptional({
    description: 'User age',
    example: 25,
    minimum: 18,
    maximum: 120,
  })
  @IsOptional()
  @IsNumber()
  @Min(18)
  @Max(120)
  age?: number;
}

// Update User DTO
export class UpdateUserDto {
  @ApiPropertyOptional({
    description: 'Updated user name',
    example: 'Jane Doe',
  })
  @IsOptional()
  @IsString()
  @Length(2, 100)
  name?: string;

  @ApiPropertyOptional({
    description: 'Updated user role',
    enum: UserRole,
    example: UserRole.MODERATOR,
  })
  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole;

  @ApiPropertyOptional({
    description: 'User status',
    enum: UserStatus,
    example: UserStatus.ACTIVE,
  })
  @IsOptional()
  @IsEnum(UserStatus)
  status?: UserStatus;
}

// User Response DTO
export class UserResponseDto {
  @ApiProperty({
    description: 'Unique user identifier',
    example: '123e4567-e89b-12d3-a456-426614174000',
    format: 'uuid',
  })
  id: string;

  @ApiProperty({
    description: 'User email address',
    example: 'user@example.com',
  })
  email: string;

  @ApiProperty({
    description: 'User full name',
    example: 'John Doe',
  })
  name: string;

  @ApiProperty({
    description: 'User role',
    enum: UserRole,
    example: UserRole.USER,
  })
  role: UserRole;

  @ApiProperty({
    description: 'User status',
    enum: UserStatus,
    example: UserStatus.ACTIVE,
  })
  status: UserStatus;

  @ApiProperty({
    description: 'Account creation timestamp',
    example: '2023-01-15T10:30:00Z',
    format: 'date-time',
  })
  createdAt: Date;

  @ApiProperty({
    description: 'Last account update timestamp',
    example: '2023-02-20T14:45:00Z',
    format: 'date-time',
  })
  updatedAt: Date;

  @ApiPropertyOptional({
    description: 'Last login timestamp',
    example: '2023-03-10T09:15:00Z',
    format: 'date-time',
  })
  lastLogin?: Date;
}

// Nested DTOs für komplexe Strukturen
export class AddressDto {
  @ApiProperty({
    description: 'Street address',
    example: '123 Main St',
  })
  @IsString()
  street: string;

  @ApiProperty({
    description: 'City name',
    example: 'New York',
  })
  @IsString()
  city: string;

  @ApiProperty({
    description: 'ZIP/Postal code',
    example: '10001',
  })
  @IsString()
  zipCode: string;

  @ApiProperty({
    description: 'Country code (ISO 3166-1 alpha-2)',
    example: 'US',
    pattern: '^[A-Z]{2}$',
  })
  @IsString()
  @Length(2, 2)
  country: string;
}

export class UserProfileDto extends UserResponseDto {
  @ApiPropertyOptional({
    description: 'User profile picture URL',
    example: 'https://example.com/avatars/user123.jpg',
    format: 'uri',
  })
  @IsOptional()
  @IsString()
  profilePicture?: string;

  @ApiPropertyOptional({
    description: 'User bio/description',
    example: 'Software developer passionate about clean code',
    maxLength: 500,
  })
  @IsOptional()
  @IsString()
  @Length(0, 500)
  bio?: string;

  @ApiPropertyOptional({
    description: 'User address information',
    type: AddressDto,
  })
  @IsOptional()
  @ValidateNested()
  @Type(() => AddressDto)
  address?: AddressDto;

  @ApiPropertyOptional({
    description: 'User tags/skills',
    type: [String],
    example: ['typescript', 'nestjs', 'mongodb'],
  })
  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  tags?: string[];
}

// Pagination DTOs
export class PaginationQueryDto {
  @ApiPropertyOptional({
    description: 'Page number (1-based)',
    example: 1,
    minimum: 1,
    default: 1,
  })
  @IsOptional()
  @Type(() => Number)
  @IsNumber()
  @Min(1)
  page?: number = 1;

  @ApiPropertyOptional({
    description: 'Number of items per page',
    example: 20,
    minimum: 1,
    maximum: 100,
    default: 20,
  })
  @IsOptional()
  @Type(() => Number)
  @IsNumber()
  @Min(1)
  @Max(100)
  limit?: number = 20;

  @ApiPropertyOptional({
    description: 'Sort field',
    example: 'createdAt',
    enum: ['name', 'email', 'createdAt', 'updatedAt'],
  })
  @IsOptional()
  @IsString()
  sortBy?: string;

  @ApiPropertyOptional({
    description: 'Sort direction',
    example: 'desc',
    enum: ['asc', 'desc'],
    default: 'desc',
  })
  @IsOptional()
  @IsEnum(['asc', 'desc'])
  sortDirection?: 'asc' | 'desc' = 'desc';
}

export class UserSearchDto extends PaginationQueryDto {
  @ApiPropertyOptional({
    description: 'Search by name or email',
    example: 'john',
  })
  @IsOptional()
  @IsString()
  search?: string;

  @ApiPropertyOptional({
    description: 'Filter by user role',
    enum: UserRole,
    example: UserRole.USER,
  })
  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole;

  @ApiPropertyOptional({
    description: 'Filter by user status',
    enum: UserStatus,
    example: UserStatus.ACTIVE,
  })
  @IsOptional()
  @IsEnum(UserStatus)
  status?: UserStatus;

  @ApiPropertyOptional({
    description: 'Filter by creation date (from)',
    example: '2023-01-01',
    format: 'date',
  })
  @IsOptional()
  @IsDateString()
  createdFrom?: string;

  @ApiPropertyOptional({
    description: 'Filter by creation date (to)',
    example: '2023-12-31',
    format: 'date',
  })
  @IsOptional()
  @IsDateString()
  createdTo?: string;
}

// Paginated Response DTO
export class PaginatedResponseDto<T> {
  @ApiProperty({
    description: 'Array of items',
    type: 'array',
  })
  data: T[];

  @ApiProperty({
    description: 'Pagination metadata',
    type: 'object',
    properties: {
      total: { type: 'number', description: 'Total number of items' },
      page: { type: 'number', description: 'Current page number' },
      limit: { type: 'number', description: 'Items per page' },
      totalPages: { type: 'number', description: 'Total number of pages' },
      hasNext: { type: 'boolean', description: 'Whether there is a next page' },
      hasPrev: { type: 'boolean', description: 'Whether there is a previous page' },
    },
  })
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
  };
}

export class PaginatedUsersResponseDto extends PaginatedResponseDto<UserResponseDto> {
  @ApiProperty({
    description: 'Array of users',
    type: [UserResponseDto],
  })
  data: UserResponseDto[];
}

22.2.2 Custom Decorators für Schema Enhancement

import { applyDecorators } from '@nestjs/common';
import { ApiProperty, ApiPropertyOptional, ApiPropertyOptions } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsOptional, ValidateNested } from 'class-validator';

// Custom API Property Decorators
export function ApiId(): PropertyDecorator {
  return ApiProperty({
    description: 'Unique identifier',
    example: '123e4567-e89b-12d3-a456-426614174000',
    format: 'uuid',
  });
}

export function ApiTimestamp(description: string): PropertyDecorator {
  return ApiProperty({
    description,
    example: '2023-01-15T10:30:00Z',
    format: 'date-time',
    type: 'string',
  });
}

export function ApiEmail(): PropertyDecorator {
  return ApiProperty({
    description: 'Email address',
    example: 'user@example.com',
    format: 'email',
  });
}

export function ApiPassword(): PropertyDecorator {
  return ApiProperty({
    description: 'Password (minimum 8 characters)',
    example: 'SecurePassword123!',
    minLength: 8,
    format: 'password',
  });
}

export function ApiPaginationQuery(): PropertyDecorator {
  return applyDecorators(
    ApiPropertyOptional({
      description: 'Page number (1-based)',
      example: 1,
      minimum: 1,
      default: 1,
    }),
    IsOptional(),
    Type(() => Number),
  );
}

export function ApiOptionalEnum<T extends Record<string, any>>(
  enumObject: T,
  description: string,
  example?: T[keyof T]
): PropertyDecorator {
  return applyDecorators(
    ApiPropertyOptional({
      description,
      enum: enumObject,
      example: example || Object.values(enumObject)[0],
    }),
    IsOptional(),
  );
}

export function ApiNestedObject<T>(
  type: () => new (...args: any[]) => T,
  description: string,
  isArray: boolean = false
): PropertyDecorator {
  const decorators = [
    ApiPropertyOptional({
      description,
      type: isArray ? [type] : type,
    }),
    IsOptional(),
    ValidateNested({ each: isArray }),
    Type(type),
  ];

  return applyDecorators(...decorators);
}

// Usage Example mit Custom Decorators
export class OptimizedUserDto {
  @ApiId()
  id: string;

  @ApiEmail()
  email: string;

  @ApiProperty({
    description: 'User full name',
    example: 'John Doe',
  })
  name: string;

  @ApiOptionalEnum(UserRole, 'User role in the system', UserRole.USER)
  role?: UserRole;

  @ApiTimestamp('Account creation timestamp')
  createdAt: Date;

  @ApiTimestamp('Last account update timestamp')
  updatedAt: Date;

  @ApiNestedObject(() => AddressDto, 'User address information')
  address?: AddressDto;

  @ApiNestedObject(() => String, 'User skills and tags', true)
  tags?: string[];
}

// Schema Extension Decorator
export function ApiSchema(options: {
  description?: string;
  example?: any;
  deprecated?: boolean;
}): ClassDecorator {
  return (target: any) => {
    // Add custom metadata to the class
    Reflect.defineMetadata('swagger:schema-options', options, target);
  };
}

// Usage
@ApiSchema({
  description: 'Comprehensive user data transfer object',
  example: {
    id: '123e4567-e89b-12d3-a456-426614174000',
    email: 'john.doe@example.com',
    name: 'John Doe',
    role: 'user',
    createdAt: '2023-01-15T10:30:00Z',
  },
})
export class EnhancedUserDto {
  // ... properties
}

22.3 API-Versioning

API-Versioning ermöglicht es, verschiedene Versionen der API parallel zu betreiben und Backwards-Kompatibilität zu gewährleisten.

22.3.1 URI-basiertes Versioning

import { Controller, Get, Post, Body, Param, Query, Version } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';

// V1 Controller
@ApiTags('Users v1')
@Controller({ path: 'users', version: '1' })
export class UsersV1Controller {
  @Get()
  @Version('1')
  @ApiOperation({ 
    summary: 'Get all users (v1)',
    description: 'Returns a simple list of users without advanced filtering',
    deprecated: true, // Markiert als deprecated
  })
  @ApiResponse({
    status: 200,
    description: 'List of users',
    schema: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          name: { type: 'string' },
          email: { type: 'string' },
        },
      },
    },
  })
  async getAllUsers(): Promise<any[]> {
    // Simplified v1 implementation
    return [
      { id: '1', name: 'John Doe', email: 'john@example.com' },
    ];
  }

  @Get(':id')
  @Version('1')
  @ApiOperation({ 
    summary: 'Get user by ID (v1)',
    deprecated: true,
  })
  @ApiParam({ name: 'id', description: 'User ID' })
  async getUserById(@Param('id') id: string): Promise<any> {
    return { id, name: 'John Doe', email: 'john@example.com' };
  }
}

// V2 Controller mit erweiterten Features
@ApiTags('Users v2')
@Controller({ path: 'users', version: '2' })
export class UsersV2Controller {
  @Get()
  @Version('2')
  @ApiOperation({ 
    summary: 'Get users with advanced filtering (v2)',
    description: 'Returns a paginated list of users with advanced search and filtering capabilities',
  })
  @ApiResponse({
    status: 200,
    description: 'Paginated users response',
    type: PaginatedUsersResponseDto,
  })
  async getAllUsers(@Query() query: UserSearchDto): Promise<PaginatedUsersResponseDto> {
    // Advanced v2 implementation with pagination and filtering
    return {
      data: [
        {
          id: '123e4567-e89b-12d3-a456-426614174000',
          email: 'john@example.com',
          name: 'John Doe',
          role: UserRole.USER,
          status: UserStatus.ACTIVE,
          createdAt: new Date('2023-01-15T10:30:00Z'),
          updatedAt: new Date('2023-02-20T14:45:00Z'),
        },
      ],
      meta: {
        total: 1,
        page: 1,
        limit: 20,
        totalPages: 1,
        hasNext: false,
        hasPrev: false,
      },
    };
  }

  @Get(':id')
  @Version('2')
  @ApiOperation({ 
    summary: 'Get detailed user by ID (v2)',
    description: 'Returns comprehensive user information including profile data',
  })
  @ApiParam({ name: 'id', description: 'User UUID', format: 'uuid' })
  @ApiResponse({
    status: 200,
    description: 'Detailed user information',
    type: UserProfileDto,
  })
  async getUserById(@Param('id') id: string): Promise<UserProfileDto> {
    // Enhanced v2 response with profile data
    return {
      id,
      email: 'john@example.com',
      name: 'John Doe',
      role: UserRole.USER,
      status: UserStatus.ACTIVE,
      createdAt: new Date('2023-01-15T10:30:00Z'),
      updatedAt: new Date('2023-02-20T14:45:00Z'),
      profilePicture: 'https://example.com/avatars/john.jpg',
      bio: 'Software developer',
      address: {
        street: '123 Main St',
        city: 'New York',
        zipCode: '10001',
        country: 'US',
      },
      tags: ['typescript', 'nestjs'],
    };
  }

  @Post()
  @Version('2')
  @ApiOperation({ 
    summary: 'Create new user (v2)',
    description: 'Creates a new user with enhanced validation and features',
  })
  @ApiResponse({
    status: 201,
    description: 'User successfully created',
    type: UserResponseDto,
  })
  async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
    // V2 implementation with enhanced features
    return {
      id: '123e4567-e89b-12d3-a456-426614174000',
      email: createUserDto.email,
      name: createUserDto.name,
      role: createUserDto.role || UserRole.USER,
      status: UserStatus.ACTIVE,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
  }
}

// Version-agnostic controller für gemeinsame Endpoints
@ApiTags('Common')
@Controller('common')
export class CommonController {
  @Get('health')
  @ApiOperation({ 
    summary: 'Health check',
    description: 'Returns API health status (version-independent)',
  })
  @ApiResponse({
    status: 200,
    description: 'API health status',
    schema: {
      type: 'object',
      properties: {
        status: { type: 'string', example: 'ok' },
        version: { type: 'string', example: '1.0.0' },
        timestamp: { type: 'string', format: 'date-time' },
      },
    },
  })
  getHealth(): any {
    return {
      status: 'ok',
      version: process.env.API_VERSION || '1.0.0',
      timestamp: new Date().toISOString(),
    };
  }
}

22.3.2 Header-basiertes Versioning

import { Controller, Get, Header } from '@nestjs/common';
import { ApiHeader, ApiOperation, ApiResponse } from '@nestjs/swagger';

@Controller('products')
export class ProductsController {
  @Get()
  @ApiOperation({ 
    summary: 'Get products',
    description: 'Returns products based on API version specified in headers',
  })
  @ApiHeader({
    name: 'API-Version',
    description: 'API Version',
    required: false,
    schema: {
      type: 'string',
      enum: ['1.0', '2.0'],
      default: '2.0',
    },
  })
  @ApiResponse({
    status: 200,
    description: 'List of products',
    headers: {
      'API-Version': {
        description: 'API version used for this response',
        schema: { type: 'string' },
      },
    },
  })
  @Header('API-Version', '2.0')
  async getProducts(@Header('API-Version') version?: string): Promise<any> {
    if (version === '1.0') {
      // Legacy format
      return [
        { id: 1, name: 'Product 1' },
      ];
    }

    // Current format (v2.0)
    return {
      data: [
        {
          id: '123e4567-e89b-12d3-a456-426614174000',
          name: 'Product 1',
          description: 'Detailed product description',
          price: 99.99,
          currency: 'USD',
          createdAt: '2023-01-15T10:30:00Z',
        },
      ],
      meta: {
        version: '2.0',
        total: 1,
      },
    };
  }
}

22.3.3 Versioning Configuration

import { INestApplication, VersioningType } from '@nestjs/common';

export function setupVersioning(app: INestApplication): void {
  // URI Versioning (default)
  app.enableVersioning({
    type: VersioningType.URI,
    prefix: 'v',
    defaultVersion: '2',
  });

  // Alternative: Header Versioning
  // app.enableVersioning({
  //   type: VersioningType.HEADER,
  //   header: 'API-Version',
  //   defaultVersion: '2',
  // });

  // Alternative: Media Type Versioning
  // app.enableVersioning({
  //   type: VersioningType.MEDIA_TYPE,
  //   key: 'version=',
  //   defaultVersion: '2',
  // });
}

// Deprecation Utility
export class ApiDeprecationService {
  static markDeprecated(version: string, deprecatedSince: string, removalDate?: string) {
    return {
      deprecated: true,
      'x-deprecated-since': deprecatedSince,
      'x-removal-date': removalDate,
      'x-deprecation-notice': `This endpoint is deprecated since version ${deprecatedSince}. ${removalDate ? `It will be removed on ${removalDate}.` : 'Please migrate to the latest version.'}`,
    };
  }
}

// Usage in Swagger Config
const deprecationInfo = ApiDeprecationService.markDeprecated('2.0', '2023-01-01', '2024-01-01');

@ApiOperation({
  summary: 'Legacy endpoint',
  ...deprecationInfo,
})

22.4 Response Examples

Detaillierte Response-Beispiele verbessern die Verständlichkeit der API erheblich.

22.4.1 Comprehensive Response Examples

import { ApiResponse, ApiExtraModels } from '@nestjs/swagger';

// Standard Error Response DTOs
export class ErrorResponseDto {
  @ApiProperty({
    description: 'HTTP status code',
    example: 400,
  })
  statusCode: number;

  @ApiProperty({
    description: 'Error message or array of validation errors',
    oneOf: [
      { type: 'string', example: 'Bad Request' },
      { 
        type: 'array',
        items: { type: 'string' },
        example: ['email must be a valid email', 'name should not be empty']
      },
    ],
  })
  message: string | string[];

  @ApiProperty({
    description: 'Error type/category',
    example: 'Bad Request',
  })
  error: string;

  @ApiProperty({
    description: 'Error timestamp',
    example: '2023-01-15T10:30:00Z',
    format: 'date-time',
  })
  timestamp: string;

  @ApiProperty({
    description: 'Request path that caused the error',
    example: '/api/v2/users',
  })
  path: string;
}

@Controller('users')
@ApiExtraModels(ErrorResponseDto) // Required for complex response examples
export class DocumentedUsersController {
  @Post()
  @ApiOperation({
    summary: 'Create a new user',
    description: 'Creates a new user account with comprehensive validation',
  })
  @ApiResponse({
    status: 201,
    description: 'User successfully created',
    type: UserResponseDto,
    example: {
      id: '123e4567-e89b-12d3-a456-426614174000',
      email: 'john.doe@example.com',
      name: 'John Doe',
      role: 'user',
      status: 'active',
      createdAt: '2023-01-15T10:30:00Z',
      updatedAt: '2023-01-15T10:30:00Z',
      lastLogin: null,
    },
  })
  @ApiResponse({
    status: 400,
    description: 'Validation failed',
    type: ErrorResponseDto,
    examples: {
      'validation-errors': {
        summary: 'Multiple validation errors',
        description: 'When multiple fields fail validation',
        value: {
          statusCode: 400,
          message: [
            'email must be a valid email',
            'name should not be empty',
            'password must be longer than or equal to 8 characters'
          ],
          error: 'Bad Request',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/v2/users',
        },
      },
      'duplicate-email': {
        summary: 'Duplicate email address',
        description: 'When trying to create a user with existing email',
        value: {
          statusCode: 400,
          message: 'User with this email already exists',
          error: 'Bad Request',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/v2/users',
        },
      },
    },
  })
  @ApiResponse({
    status: 401,
    description: 'Authentication required',
    type: ErrorResponseDto,
    example: {
      statusCode: 401,
      message: 'Unauthorized',
      error: 'Unauthorized',
      timestamp: '2023-01-15T10:30:00Z',
      path: '/api/v2/users',
    },
  })
  @ApiResponse({
    status: 403,
    description: 'Insufficient permissions',
    type: ErrorResponseDto,
    example: {
      statusCode: 403,
      message: 'Insufficient permissions to create users',
      error: 'Forbidden',
      timestamp: '2023-01-15T10:30:00Z',
      path: '/api/v2/users',
    },
  })
  @ApiResponse({
    status: 429,
    description: 'Rate limit exceeded',
    type: ErrorResponseDto,
    example: {
      statusCode: 429,
      message: 'Too Many Requests',
      error: 'Too Many Requests',
      timestamp: '2023-01-15T10:30:00Z',
      path: '/api/v2/users',
    },
  })
  async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
    // Implementation
    return {} as UserResponseDto;
  }

  @Get()
  @ApiOperation({
    summary: 'Get users with filtering and pagination',
    description: 'Retrieves a paginated list of users with optional filtering by role, status, and search terms',
  })
  @ApiResponse({
    status: 200,
    description: 'Paginated users list',
    type: PaginatedUsersResponseDto,
    examples: {
      'with-data': {
        summary: 'Successful response with users',
        description: 'Normal response when users are found',
        value: {
          data: [
            {
              id: '123e4567-e89b-12d3-a456-426614174000',
              email: 'john.doe@example.com',
              name: 'John Doe',
              role: 'user',
              status: 'active',
              createdAt: '2023-01-15T10:30:00Z',
              updatedAt: '2023-01-15T10:30:00Z',
              lastLogin: '2023-03-10T09:15:00Z',
            },
            {
              id: '456e7890-e12b-34d5-a678-901234567890',
              email: 'jane.smith@example.com',
              name: 'Jane Smith',
              role: 'moderator',
              status: 'active',
              createdAt: '2023-02-01T14:20:00Z',
              updatedAt: '2023-03-05T11:30:00Z',
              lastLogin: '2023-03-09T16:45:00Z',
            },
          ],
          meta: {
            total: 150,
            page: 1,
            limit: 20,
            totalPages: 8,
            hasNext: true,
            hasPrev: false,
          },
        },
      },
      'empty-result': {
        summary: 'Empty result set',
        description: 'When no users match the search criteria',
        value: {
          data: [],
          meta: {
            total: 0,
            page: 1,
            limit: 20,
            totalPages: 0,
            hasNext: false,
            hasPrev: false,
          },
        },
      },
    },
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid query parameters',
    type: ErrorResponseDto,
    examples: {
      'invalid-pagination': {
        summary: 'Invalid pagination parameters',
        value: {
          statusCode: 400,
          message: ['page must be a positive number', 'limit must not be greater than 100'],
          error: 'Bad Request',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/v2/users',
        },
      },
      'invalid-date-range': {
        summary: 'Invalid date range',
        value: {
          statusCode: 400,
          message: 'createdFrom must be earlier than createdTo',
          error: 'Bad Request',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/v2/users',
        },
      },
    },
  })
  async getUsers(@Query() query: UserSearchDto): Promise<PaginatedUsersResponseDto> {
    // Implementation
    return {} as PaginatedUsersResponseDto;
  }

  @Get(':id')
  @ApiOperation({
    summary: 'Get user by ID',
    description: 'Retrieves detailed information about a specific user',
  })
  @ApiResponse({
    status: 200,
    description: 'User details',
    type: UserProfileDto,
    example: {
      id: '123e4567-e89b-12d3-a456-426614174000',
      email: 'john.doe@example.com',
      name: 'John Doe',
      role: 'user',
      status: 'active',
      createdAt: '2023-01-15T10:30:00Z',
      updatedAt: '2023-02-20T14:45:00Z',
      lastLogin: '2023-03-10T09:15:00Z',
      profilePicture: 'https://example.com/avatars/john-doe.jpg',
      bio: 'Full-stack developer with 5 years of experience in TypeScript and Node.js',
      address: {
        street: '123 Main Street',
        city: 'New York',
        zipCode: '10001',
        country: 'US',
      },
      tags: ['typescript', 'nestjs', 'react', 'mongodb'],
    },
  })
  @ApiResponse({
    status: 404,
    description: 'User not found',
    type: ErrorResponseDto,
    example: {
      statusCode: 404,
      message: 'User with ID 123e4567-e89b-12d3-a456-426614174000 not found',
      error: 'Not Found',
      timestamp: '2023-01-15T10:30:00Z',
      path: '/api/v2/users/123e4567-e89b-12d3-a456-426614174000',
    },
  })
  async getUserById(@Param('id') id: string): Promise<UserProfileDto> {
    // Implementation
    return {} as UserProfileDto;
  }
}

22.4.2 Dynamic Response Examples

import { ApiResponseOptions } from '@nestjs/swagger';

export class ResponseExamplesService {
  // Utility für dynamische Examples
  static createSuccessExample<T>(data: T, description?: string): ApiResponseOptions {
    return {
      status: 200,
      description: description || 'Successful response',
      content: {
        'application/json': {
          example: data,
          schema: {
            type: 'object',
          },
        },
      },
    };
  }

  static createErrorExample(
    status: number,
    message: string,
    path: string,
    description?: string
  ): ApiResponseOptions {
    return {
      status,
      description: description || 'Error response',
      content: {
        'application/json': {
          example: {
            statusCode: status,
            message,
            error: this.getErrorName(status),
            timestamp: new Date().toISOString(),
            path,
          },
        },
      },
    };
  }

  private static getErrorName(status: number): string {
    const errorNames: Record<number, string> = {
      400: 'Bad Request',
      401: 'Unauthorized',
      403: 'Forbidden',
      404: 'Not Found',
      409: 'Conflict',
      422: 'Unprocessable Entity',
      429: 'Too Many Requests',
      500: 'Internal Server Error',
    };
    
    return errorNames[status] || 'Error';
  }

  // Environment-specific Examples
  static getExampleBasedOnEnvironment(): any {
    const isDevelopment = process.env.NODE_ENV === 'development';
    
    if (isDevelopment) {
      return {
        // Detailed examples for development
        id: '123e4567-e89b-12d3-a456-426614174000',
        email: 'john.doe@example.com',
        name: 'John Doe',
        debug: {
          createdBy: 'system',
          executionTime: '45ms',
          queryCount: 3,
        },
      };
    }

    return {
      // Simplified examples for production
      id: '123e4567-e89b-12d3-a456-426614174000',
      email: 'john.doe@example.com',
      name: 'John Doe',
    };
  }
}

// Custom Decorator für Response Examples
export function ApiSuccessResponse<T>(
  type: new (...args: any[]) => T,
  description?: string,
  example?: any
): MethodDecorator {
  return applyDecorators(
    ApiResponse({
      status: 200,
      description: description || 'Successful response',
      type,
      example: example || ResponseExamplesService.getExampleBasedOnEnvironment(),
    })
  );
}

export function ApiErrorResponses(path: string): MethodDecorator {
  return applyDecorators(
    ApiResponse(ResponseExamplesService.createErrorExample(400, 'Validation failed', path, 'Invalid request data')),
    ApiResponse(ResponseExamplesService.createErrorExample(401, 'Unauthorized', path, 'Authentication required')),
    ApiResponse(ResponseExamplesService.createErrorExample(403, 'Forbidden', path, 'Insufficient permissions')),
    ApiResponse(ResponseExamplesService.createErrorExample(429, 'Too Many Requests', path, 'Rate limit exceeded')),
    ApiResponse(ResponseExamplesService.createErrorExample(500, 'Internal Server Error', path, 'Server error occurred')),
  );
}

// Usage
@Controller('products')
export class ProductsController {
  @Get(':id')
  @ApiSuccessResponse(ProductDto, 'Product details retrieved successfully', {
    id: 'prod_123',
    name: 'Sample Product',
    price: 99.99,
    description: 'A sample product for demonstration',
  })
  @ApiErrorResponses('/api/v2/products/:id')
  async getProduct(@Param('id') id: string): Promise<ProductDto> {
    // Implementation
    return {} as ProductDto;
  }
}

22.5 Authentication Documentation

Umfassende Dokumentation der Authentifizierungsmechanismen ist kritisch für API-Konsumenten.

22.5.1 JWT Authentication Documentation

import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { 
  ApiTags, 
  ApiOperation, 
  ApiResponse, 
  ApiBearerAuth, 
  ApiSecurity,
  ApiBody,
  ApiUnauthorizedResponse 
} from '@nestjs/swagger';

// Authentication DTOs
export class LoginDto {
  @ApiProperty({
    description: 'User email address',
    example: 'user@example.com',
    format: 'email',
  })
  @IsEmail()
  email: string;

  @ApiProperty({
    description: 'User password',
    example: 'SecurePassword123!',
    format: 'password',
    minLength: 8,
  })
  @IsString()
  @Length(8, 128)
  password: string;

  @ApiPropertyOptional({
    description: 'Remember me option for extended session',
    example: false,
    default: false,
  })
  @IsOptional()
  @IsBoolean()
  rememberMe?: boolean = false;
}

export class LoginResponseDto {
  @ApiProperty({
    description: 'JWT access token',
    example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  })
  accessToken: string;

  @ApiProperty({
    description: 'JWT refresh token',
    example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  })
  refreshToken: string;

  @ApiProperty({
    description: 'Token type',
    example: 'Bearer',
    default: 'Bearer',
  })
  tokenType: string;

  @ApiProperty({
    description: 'Token expiration time in seconds',
    example: 3600,
  })
  expiresIn: number;

  @ApiProperty({
    description: 'User information',
    type: UserResponseDto,
  })
  user: UserResponseDto;
}

export class RefreshTokenDto {
  @ApiProperty({
    description: 'JWT refresh token',
    example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  })
  @IsString()
  refreshToken: string;
}

@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
  @Post('login')
  @ApiOperation({
    summary: 'User login',
    description: `
Authenticates a user and returns JWT tokens for API access.

### Authentication Flow:
1. Send POST request with email and password
2. Receive access token (short-lived) and refresh token (long-lived)
3. Use access token in Authorization header: \`Bearer <token>\`
4. When access token expires, use refresh token to get new tokens

### Token Expiration:
- **Access Token**: 15 minutes
- **Refresh Token**: 7 days (or 30 days with rememberMe=true)

### Security Features:
- Rate limiting: 5 attempts per minute per IP
- Account lockout: 30 minutes after 5 failed attempts
- Password strength validation
- IP address logging for security audit
    `,
  })
  @ApiBody({
    type: LoginDto,
    description: 'Login credentials',
    examples: {
      'standard-login': {
        summary: 'Standard login',
        description: 'Regular user login',
        value: {
          email: 'user@example.com',
          password: 'SecurePassword123!',
          rememberMe: false,
        },
      },
      'remember-me-login': {
        summary: 'Login with remember me',
        description: 'Extended session login',
        value: {
          email: 'user@example.com',
          password: 'SecurePassword123!',
          rememberMe: true,
        },
      },
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Login successful',
    type: LoginResponseDto,
    example: {
      accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
      refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidHlwZSI6InJlZnJlc2giLCJpYXQiOjE1MTYyMzkwMjJ9.XYZ123...',
      tokenType: 'Bearer',
      expiresIn: 900,
      user: {
        id: '123e4567-e89b-12d3-a456-426614174000',
        email: 'user@example.com',
        name: 'John Doe',
        role: 'user',
        status: 'active',
        createdAt: '2023-01-15T10:30:00Z',
        updatedAt: '2023-02-20T14:45:00Z',
      },
    },
  })
  @ApiResponse({
    status: 401,
    description: 'Invalid credentials',
    content: {
      'application/json': {
        examples: {
          'invalid-credentials': {
            summary: 'Wrong email or password',
            value: {
              statusCode: 401,
              message: 'Invalid email or password',
              error: 'Unauthorized',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/auth/login',
            },
          },
          'account-locked': {
            summary: 'Account locked due to failed attempts',
            value: {
              statusCode: 401,
              message: 'Account temporarily locked due to too many failed login attempts. Try again in 30 minutes.',
              error: 'Account Locked',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/auth/login',
              retryAfter: 1800,
            },
          },
          'account-disabled': {
            summary: 'Account disabled',
            value: {
              statusCode: 401,
              message: 'Account has been disabled. Please contact support.',
              error: 'Account Disabled',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/auth/login',
            },
          },
        },
      },
    },
  })
  @ApiResponse({
    status: 429,
    description: 'Too many login attempts',
    content: {
      'application/json': {
        example: {
          statusCode: 429,
          message: 'Too many login attempts. Please try again later.',
          error: 'Too Many Requests',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/auth/login',
          retryAfter: 60,
        },
      },
    },
  })
  async login(@Body() loginDto: LoginDto): Promise<LoginResponseDto> {
    // Implementation
    return {} as LoginResponseDto;
  }

  @Post('refresh')
  @ApiOperation({
    summary: 'Refresh access token',
    description: `
Generates a new access token using a valid refresh token.

### Usage:
1. When access token expires (401 response)
2. Use refresh token to get new access token
3. Continue using new access token for API calls

### Important Notes:
- Refresh tokens are single-use (new refresh token provided)
- Refresh tokens expire after 7 days (30 days with rememberMe)
- Invalid refresh tokens result in logout requirement
    `,
  })
  @ApiBody({
    type: RefreshTokenDto,
    description: 'Refresh token',
    example: {
      refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Tokens refreshed successfully',
    type: LoginResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Invalid or expired refresh token',
    content: {
      'application/json': {
        examples: {
          'expired-token': {
            summary: 'Refresh token expired',
            value: {
              statusCode: 401,
              message: 'Refresh token has expired. Please log in again.',
              error: 'Token Expired',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/auth/refresh',
            },
          },
          'invalid-token': {
            summary: 'Invalid refresh token',
            value: {
              statusCode: 401,
              message: 'Invalid refresh token',
              error: 'Invalid Token',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/auth/refresh',
            },
          },
        },
      },
    },
  })
  async refresh(@Body() refreshDto: RefreshTokenDto): Promise<LoginResponseDto> {
    // Implementation
    return {} as LoginResponseDto;
  }

  @Post('logout')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({
    summary: 'User logout',
    description: `
Invalidates the current JWT tokens and logs out the user.

### Security Features:
- Blacklists current access token
- Invalidates refresh token
- Clears user session data
- Logs logout event for audit trail

### Headers Required:
\`\`\`
Authorization: Bearer <access_token>
\`\`\`
    `,
  })
  @ApiResponse({
    status: 200,
    description: 'Logout successful',
    content: {
      'application/json': {
        example: {
          message: 'Logout successful',
          timestamp: '2023-01-15T10:30:00Z',
        },
      },
    },
  })
  @ApiUnauthorizedResponse({
    description: 'Access token required',
    content: {
      'application/json': {
        example: {
          statusCode: 401,
          message: 'Access token required',
          error: 'Unauthorized',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/auth/logout',
        },
      },
    },
  })
  async logout(): Promise<{ message: string; timestamp: string }> {
    // Implementation
    return {
      message: 'Logout successful',
      timestamp: new Date().toISOString(),
    };
  }
}

// Protected Endpoint Example
@ApiTags('Protected Resources')
@Controller('profile')
export class ProfileController {
  @Get()
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({
    summary: 'Get user profile',
    description: `
Retrieves the authenticated user's profile information.

### Authentication:
This endpoint requires a valid JWT access token in the Authorization header.

### Usage:
\`\`\`
GET /api/profile
Authorization: Bearer <your_access_token>
\`\`\`

### Token Validation:
- Token must be valid and not expired
- Token must not be blacklisted
- User account must be active
    `,
  })
  @ApiResponse({
    status: 200,
    description: 'User profile retrieved successfully',
    type: UserProfileDto,
  })
  @ApiUnauthorizedResponse({
    description: 'Authentication failed',
    content: {
      'application/json': {
        examples: {
          'missing-token': {
            summary: 'Missing authorization header',
            value: {
              statusCode: 401,
              message: 'Authorization header missing',
              error: 'Unauthorized',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/profile',
            },
          },
          'invalid-token': {
            summary: 'Invalid or expired token',
            value: {
              statusCode: 401,
              message: 'Invalid or expired access token',
              error: 'Token Invalid',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/profile',
            },
          },
          'blacklisted-token': {
            summary: 'Blacklisted token',
            value: {
              statusCode: 401,
              message: 'Token has been revoked',
              error: 'Token Revoked',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/profile',
            },
          },
        },
      },
    },
  })
  async getProfile(): Promise<UserProfileDto> {
    // Implementation
    return {} as UserProfileDto;
  }
}

22.5.2 API Key Authentication Documentation

@Controller('public-api')
@ApiTags('Public API')
export class PublicApiController {
  @Get('data')
  @UseGuards(ApiKeyGuard)
  @ApiSecurity('api-key')
  @ApiOperation({
    summary: 'Get public data',
    description: `
Access public data using API key authentication.

### API Key Authentication:
1. Obtain API key from your account dashboard
2. Include key in request header: \`X-API-Key: your_api_key\`
3. API keys have different rate limits based on plan

### Rate Limits by Plan:
- **Free**: 100 requests/hour
- **Basic**: 1,000 requests/hour  
- **Premium**: 10,000 requests/hour
- **Enterprise**: Custom limits

### Example Request:
\`\`\`bash
curl -H "X-API-Key: sk_live_abc123..." \\
     "https://api.example.com/public-api/data"
\`\`\`

### Security Notes:
- Keep your API key secret
- Use HTTPS for all requests
- Rotate keys regularly
- Monitor usage in dashboard
    `,
  })
  @ApiResponse({
    status: 200,
    description: 'Data retrieved successfully',
    content: {
      'application/json': {
        example: {
          data: [
            { id: 1, name: 'Sample Data 1' },
            { id: 2, name: 'Sample Data 2' },
          ],
          meta: {
            total: 2,
            rateLimit: {
              limit: 1000,
              remaining: 998,
              resetTime: '2023-01-15T11:00:00Z',
            },
          },
        },
      },
    },
    headers: {
      'X-RateLimit-Limit': {
        description: 'Rate limit for your API key',
        schema: { type: 'integer', example: 1000 },
      },
      'X-RateLimit-Remaining': {
        description: 'Remaining requests in current window',
        schema: { type: 'integer', example: 998 },
      },
      'X-RateLimit-Reset': {
        description: 'Rate limit reset time (Unix timestamp)',
        schema: { type: 'integer', example: 1673776800 },
      },
    },
  })
  @ApiResponse({
    status: 401,
    description: 'Invalid or missing API key',
    content: {
      'application/json': {
        examples: {
          'missing-key': {
            summary: 'Missing API key header',
            value: {
              statusCode: 401,
              message: 'API key required in X-API-Key header',
              error: 'API Key Missing',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/public-api/data',
            },
          },
          'invalid-key': {
            summary: 'Invalid API key',
            value: {
              statusCode: 401,
              message: 'Invalid API key',
              error: 'Invalid API Key',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/public-api/data',
            },
          },
          'suspended-key': {
            summary: 'Suspended API key',
            value: {
              statusCode: 401,
              message: 'API key has been suspended. Please contact support.',
              error: 'API Key Suspended',
              timestamp: '2023-01-15T10:30:00Z',
              path: '/api/public-api/data',
            },
          },
        },
      },
    },
  })
  @ApiResponse({
    status: 429,
    description: 'Rate limit exceeded',
    content: {
      'application/json': {
        example: {
          statusCode: 429,
          message: 'Rate limit exceeded. Limit: 1000 requests per hour.',
          error: 'Too Many Requests',
          timestamp: '2023-01-15T10:30:00Z',
          path: '/api/public-api/data',
          retryAfter: 3600,
          rateLimit: {
            limit: 1000,
            remaining: 0,
            resetTime: '2023-01-15T11:00:00Z',
          },
        },
      },
    },
    headers: {
      'Retry-After': {
        description: 'Seconds until rate limit resets',
        schema: { type: 'integer', example: 3600 },
      },
    },
  })
  async getData(): Promise<any> {
    // Implementation
    return {};
  }
}

22.6 Testing über Swagger UI

Swagger UI bietet eine interaktive Testumgebung für API-Endpoints direkt in der Dokumentation.

22.6.1 Enhanced Swagger UI Configuration

import { INestApplication } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

export class InteractiveSwaggerSetup {
  static configure(app: INestApplication): void {
    const config = new DocumentBuilder()
      .setTitle('Interactive API Documentation')
      .setDescription('Comprehensive API with built-in testing capabilities')
      .setVersion('1.0.0')
      .addBearerAuth({
        type: 'http',
        scheme: 'bearer',
        bearerFormat: 'JWT',
        name: 'JWT',
        description: `
### How to test protected endpoints:

1. **Login to get JWT token:**
   - Go to **Authentication > POST /auth/login**
   - Click "Try it out"
   - Enter your credentials
   - Click "Execute"
   - Copy the \`accessToken\` from the response

2. **Authorize for protected endpoints:**
   - Click the "Authorize" button 🔒 at the top
   - Paste your token in the "Value" field
   - Click "Authorize"
   - Click "Close"

3. **Test protected endpoints:**
   - All protected endpoints will now include your JWT token
   - The token is automatically added to the Authorization header
        `,
        in: 'header',
      })
      .addApiKey({
        type: 'apiKey',
        name: 'X-API-Key',
        in: 'header',
        description: `
### API Key Testing:

1. **Get your API key:**
   - Sign up for an account
   - Go to your dashboard
   - Copy your API key (starts with \`sk_live_\` or \`sk_test_\`)

2. **Authorize with API key:**
   - Click the "Authorize" button 🔒
   - Paste your API key in the "api-key" field
   - Click "Authorize"
        `,
      }, 'api-key')
      .build();

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

    // Enhanced Swagger UI with testing features
    SwaggerModule.setup('api/docs', app, document, {
      customSiteTitle: 'API Documentation & Testing',
      customfavIcon: '/favicon.ico',
      customCss: `
        .swagger-ui .topbar { 
          background-color: #1976d2; 
          padding: 10px 0;
        }
        .swagger-ui .topbar-wrapper .link { 
          color: white; 
          font-weight: bold;
        }
        .swagger-ui .info .title { 
          color: #1976d2; 
        }
        .swagger-ui .auth-wrapper {
          background: #f7f7f7;
          border: 1px solid #d3d3d3;
          border-radius: 4px;
          margin: 10px 0;
          padding: 10px;
        }
        .swagger-ui .auth-container .auth-wrapper h4 {
          color: #1976d2;
          margin-bottom: 10px;
        }
        .swagger-ui .model-example {
          background: #f7f7f7;
        }
        .response-controls {
          margin-top: 10px;
          padding: 10px;
          background: #f0f8ff;
          border-radius: 4px;
        }
      `,
      customJs: [
        // Custom JavaScript für erweiterte Funktionalität
        '/swagger-custom.js',
      ],
      swaggerOptions: {
        // UI Konfiguration für bessere Testability
        persistAuthorization: true,
        displayRequestDuration: true,
        tryItOutEnabled: true,
        requestInterceptor: (req: any) => {
          // Log all requests for debugging
          console.log('Swagger Request:', req);
          return req;
        },
        responseInterceptor: (res: any) => {
          // Log all responses for debugging
          console.log('Swagger Response:', res);
          return res;
        },
        docExpansion: 'list',
        filter: true,
        showExtensions: true,
        showCommonExtensions: true,
        defaultModelsExpandDepth: 2,
        defaultModelExpandDepth: 2,
        validatorUrl: null, // Disable online validator
        
        // Request/Response logging
        requestSnippetsEnabled: true,
        requestSnippets: {
          generators: {
            curl_bash: {
              title: 'cURL (bash)',
              syntax: 'bash',
            },
            curl_powershell: {
              title: 'cURL (PowerShell)',
              syntax: 'powershell',
            },
            curl_cmd: {
              title: 'cURL (CMD)',
              syntax: 'bash',
            },
          },
          defaultExpanded: true,
          languages: ['curl_bash', 'curl_powershell'],
        },
      },
    });

    // Separate endpoint for JSON schema
    app.use('/api/docs-json', (req, res) => {
      res.setHeader('Content-Type', 'application/json');
      res.send(document);
    });

    // Testing utilities endpoint
    this.setupTestingUtilities(app);
  }

  private static setupTestingUtilities(app: INestApplication): void {
    // Custom middleware für Test-Token-Generierung
    app.use('/api/test-utils/generate-token', (req, res) => {
      if (process.env.NODE_ENV !== 'production') {
        // Nur in Development/Testing verfügbar
        const testToken = this.generateTestToken();
        res.json({
          accessToken: testToken,
          instructions: [
            '1. Copy the access token above',
            '2. Click the "Authorize" button in Swagger UI',
            '3. Paste the token in the JWT field',
            '4. Test protected endpoints!',
          ],
        });
      } else {
        res.status(404).json({ message: 'Not available in production' });
      }
    });

    // Endpoint für Test-Daten-Generierung
    app.use('/api/test-utils/sample-data', (req, res) => {
      if (process.env.NODE_ENV !== 'production') {
        res.json({
          sampleUser: {
            email: 'test@example.com',
            password: 'TestPassword123!',
            name: 'Test User',
            role: 'user',
          },
          sampleProduct: {
            name: 'Sample Product',
            description: 'A sample product for testing',
            price: 99.99,
            category: 'electronics',
          },
          instructions: [
            'Use these sample data objects to test POST/PUT endpoints',
            'Copy and paste into the request body fields',
            'Modify as needed for your tests',
          ],
        });
      } else {
        res.status(404).json({ message: 'Not available in production' });
      }
    });
  }

  private static generateTestToken(): string {
    // Simplified test token generation
    // In real implementation, use proper JWT library
    const payload = {
      sub: 'test-user-id',
      email: 'test@example.com',
      role: 'user',
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
    };
    
    return `test-token-${Buffer.from(JSON.stringify(payload)).toString('base64')}`;
  }
}

// Custom Swagger JavaScript (swagger-custom.js)
const swaggerCustomJs = `
// Add custom functionality to Swagger UI
(function() {
  'use strict';

  // Add testing helpers
  function addTestingHelpers() {
    // Quick fill buttons for common test data
    const addQuickFillButton = (formElement, data) => {
      const button = document.createElement('button');
      button.innerHTML = '📝 Quick Fill';
      button.type = 'button';
      button.style.marginLeft = '10px';
      button.style.padding = '5px 10px';
      button.style.backgroundColor = '#1976d2';
      button.style.color = 'white';
      button.style.border = 'none';
      button.style.borderRadius = '3px';
      button.style.cursor = 'pointer';
      
      button.addEventListener('click', () => {
        const textarea = formElement.querySelector('textarea');
        if (textarea) {
          textarea.value = JSON.stringify(data, null, 2);
          // Trigger change event
          textarea.dispatchEvent(new Event('input', { bubbles: true }));
        }
      });
      
      return button;
    };

    // Sample data for quick fill
    const sampleData = {
      login: {
        email: 'test@example.com',
        password: 'TestPassword123!',
        rememberMe: false
      },
      createUser: {
        email: 'newuser@example.com',
        name: 'New User',
        password: 'SecurePassword123!',
        role: 'user',
        age: 25
      },
      updateUser: {
        name: 'Updated Name',
        role: 'moderator',
        status: 'active'
      }
    };

    // Add buttons to relevant endpoints
    setTimeout(() => {
      document.querySelectorAll('.opblock').forEach(block => {
        const summary = block.querySelector('.opblock-summary-description');
        if (summary) {
          const operation = summary.textContent.toLowerCase();
          let data = null;
          
          if (operation.includes('login')) data = sampleData.login;
          else if (operation.includes('create') && operation.includes('user')) data = sampleData.createUser;
          else if (operation.includes('update') && operation.includes('user')) data = sampleData.updateUser;
          
          if (data) {
            const tryItButton = block.querySelector('.try-out__btn');
            if (tryItButton) {
              const quickFillButton = addQuickFillButton(block, data);
              tryItButton.parentNode.appendChild(quickFillButton);
            }
          }
        }
      });
    }, 2000);
  }

  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', addTestingHelpers);
  } else {
    addTestingHelpers();
  }

  // Response time tracking
  const originalFetch = window.fetch;
  window.fetch = function(...args) {
    const startTime = performance.now();
    return originalFetch.apply(this, args).then(response => {
      const endTime = performance.now();
      const duration = Math.round(endTime - startTime);
      console.log(\`API Request completed in \${duration}ms\`);
      
      // Add response time to UI if possible
      setTimeout(() => {
        const responseSection = document.querySelector('.response');
        if (responseSection && !responseSection.querySelector('.response-time')) {
          const timeElement = document.createElement('div');
          timeElement.className = 'response-time';
          timeElement.innerHTML = \`<strong>Response Time:</strong> \${duration}ms\`;
          timeElement.style.padding = '10px';
          timeElement.style.backgroundColor = '#e8f5e8';
          timeElement.style.border = '1px solid #4caf50';
          timeElement.style.borderRadius = '4px';
          timeElement.style.marginBottom = '10px';
          responseSection.insertBefore(timeElement, responseSection.firstChild);
        }
      }, 100);
      
      return response;
    });
  };

  console.log('Swagger UI custom enhancements loaded');
})();
`;

// Service für Custom Testing Features
export class SwaggerTestingService {
  static getTestingInstructions(): string {
    return `
# API Testing Guide

## Quick Start
1. **Authentication Testing:**
   - Use the test token generator: \`GET /api/test-utils/generate-token\`
   - Or login with: \`test@example.com / TestPassword123!\`

2. **Sample Data:**
   - Get sample payloads: \`GET /api/test-utils/sample-data\`
   - Use "Quick Fill" buttons in Swagger UI

3. **Testing Workflow:**
   - Start with authentication endpoints
   - Test CRUD operations in logical order
   - Verify error responses with invalid data

## Advanced Testing
- **Rate Limiting:** Make rapid requests to test limits
- **Validation:** Try invalid data formats
- **Authorization:** Test with different user roles
- **Edge Cases:** Empty payloads, special characters

## Debugging Tips
- Check browser console for detailed logs
- Response times are displayed automatically
- Use network tab to inspect headers
- Export API calls as cURL commands
    `;
  }
}

API-Dokumentation ist ein kritischer Erfolgsfaktor für jede API. Mit NestJS und OpenAPI/Swagger können Sie automatisch umfassende, interaktive Dokumentationen erstellen, die nicht nur als Referenz dienen, sondern auch als vollwertige Testumgebung fungieren. Die Kombination aus automatischer Schema-Generierung, detaillierten Beispielen und interaktiven Testmöglichkeiten macht Ihre API für Entwickler zugänglich und reduziert Support-Anfragen erheblich.