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.
Die Integration von OpenAPI (ehemals Swagger) in NestJS ermöglicht es, aus TypeScript-Code automatisch umfassende API-Dokumentationen zu generieren.
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();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,
},
});
}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',
});
}
}TypeScript-Typen und Decorators ermöglichen die automatische Generierung präziser OpenAPI-Schemas.
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[];
}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
}API-Versioning ermöglicht es, verschiedene Versionen der API parallel zu betreiben und Backwards-Kompatibilität zu gewährleisten.
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(),
};
}
}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,
},
};
}
}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,
})Detaillierte Response-Beispiele verbessern die Verständlichkeit der API erheblich.
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;
}
}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;
}
}Umfassende Dokumentation der Authentifizierungsmechanismen ist kritisch für API-Konsumenten.
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;
}
}@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 {};
}
}Swagger UI bietet eine interaktive Testumgebung für API-Endpoints direkt in der Dokumentation.
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.