feat(swagger): add OpenAPI documentation to calendar, contacts, and todo backends

- Extend shared-nestjs-setup bootstrapApp with optional swagger config
- Auto-setup Swagger/OpenAPI when swagger: true is passed
- Add @nestjs/swagger as optional peer dependency
- Enable swagger in calendar (:3014/api/docs), contacts (:3015/api/docs), todo (:3018/api/docs)
- Migrate todo main.ts from custom bootstrap to shared bootstrapApp
- JWT Bearer auth configured in Swagger UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-19 12:38:28 +01:00
parent 4c342a53e8
commit 217c48663b
9 changed files with 94 additions and 69 deletions

View file

@ -33,6 +33,7 @@
"@nestjs/core": "^10.4.15",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/schedule": "^4.1.2",
"@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.2.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",

View file

@ -5,4 +5,5 @@ bootstrapApp(AppModule, {
defaultPort: 3014,
serviceName: 'Calendar',
additionalCorsOrigins: ['http://localhost:5179'],
swagger: true,
});

View file

@ -30,6 +30,7 @@
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.15",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.2.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",

View file

@ -5,4 +5,5 @@ bootstrapApp(AppModule, {
defaultPort: 3015,
serviceName: 'Contacts',
additionalCorsOrigins: ['http://localhost:5184'],
swagger: true,
});

View file

@ -28,6 +28,7 @@
"@nestjs/core": "^10.4.9",
"@nestjs/platform-express": "^10.4.9",
"@nestjs/schedule": "^4.1.2",
"@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.2.1",
"@todo/shared": "workspace:*",
"class-transformer": "^0.5.1",

View file

@ -1,67 +1,9 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { bootstrapApp } from '@manacore/shared-nestjs-setup';
import { AppModule } from './app.module';
async function bootstrap() {
const logger = new Logger('Bootstrap');
const app = await NestFactory.create(AppModule);
// Enable CORS for all platforms
app.enableCors({
origin: (origin, callback) => {
// Allow requests with no origin (mobile apps, curl, etc.)
if (!origin) {
callback(null, true);
return;
}
const allowedOrigins = process.env.CORS_ORIGINS?.split(',') || [
'http://localhost:5173',
'http://localhost:5186',
'http://localhost:8081',
'http://localhost:19006',
];
// Allow all localhost ports in development
if (process.env.NODE_ENV === 'development' && origin.includes('localhost')) {
callback(null, true);
return;
}
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
logger.warn(`Blocked request from origin: ${origin}`);
callback(new Error('Not allowed by CORS'), false);
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
// Global validation pipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
})
);
// API prefix (exclude metrics endpoint for Prometheus scraping)
app.setGlobalPrefix('api/v1', {
exclude: ['metrics', 'health'],
});
const port = process.env.PORT || 3017;
await app.listen(port);
logger.log(`Todo API is running on: http://localhost:${port}`);
logger.log(`Health check: http://localhost:${port}/health`);
}
bootstrap();
bootstrapApp(AppModule, {
defaultPort: 3018,
serviceName: 'Todo',
additionalCorsOrigins: ['http://localhost:5186', 'http://localhost:5188'],
swagger: true,
});

View file

@ -30,6 +30,12 @@
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
"@nestjs/core": "^10.0.0 || ^11.0.0",
"@nestjs/swagger": "^8.0.0 || ^7.0.0"
},
"peerDependenciesMeta": {
"@nestjs/swagger": {
"optional": true
}
}
}

View file

@ -1,8 +1,8 @@
/**
* Shared NestJS Bootstrap Utilities for ManaCore Backends
*
* Provides a consistent setup for CORS, validation, and global prefix
* across all backend applications.
* Provides a consistent setup for CORS, validation, global prefix,
* and optional Swagger/OpenAPI documentation across all backend applications.
*/
import { type INestApplication, ValidationPipe, type Type } from '@nestjs/common';
@ -21,6 +21,20 @@ export const DEFAULT_CORS_ORIGINS = [
'exp://localhost:8081', // Expo native
];
/**
* Swagger/OpenAPI configuration
*/
export interface SwaggerOptions {
/** API title (default: serviceName + ' API') */
title?: string;
/** API description */
description?: string;
/** API version (default: '1.0') */
version?: string;
/** Path to serve docs at (default: 'api/docs') */
path?: string;
}
/**
* Configuration options for the bootstrap utility
*/
@ -39,6 +53,8 @@ export interface BootstrapOptions {
corsMethods?: string[];
/** Body size limit for JSON/urlencoded payloads (default: '100kb'). Use '50mb' for image uploads. */
bodyLimit?: string;
/** Enable Swagger/OpenAPI docs. Pass true for defaults or SwaggerOptions for custom config. */
swagger?: boolean | SwaggerOptions;
}
/**
@ -93,6 +109,38 @@ export function configurePrefix(
app.setGlobalPrefix(prefix, { exclude });
}
/**
* Setup Swagger/OpenAPI documentation if enabled and @nestjs/swagger is installed
*/
async function setupSwagger(
app: INestApplication,
serviceName: string,
port: string | number,
swaggerConfig: boolean | SwaggerOptions
): Promise<void> {
try {
const { DocumentBuilder, SwaggerModule } = await import('@nestjs/swagger');
const opts: SwaggerOptions = typeof swaggerConfig === 'object' ? swaggerConfig : {};
const config = new DocumentBuilder()
.setTitle(opts.title || `${serviceName} API`)
.setDescription(opts.description || `API documentation for ${serviceName}`)
.setVersion(opts.version || '1.0')
.addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'JWT-auth')
.build();
const document = SwaggerModule.createDocument(app, config);
const docsPath = opts.path || 'api/docs';
SwaggerModule.setup(docsPath, app, document, {
swaggerOptions: { persistAuthorization: true },
});
console.log(`${serviceName} API docs at http://localhost:${port}/${docsPath}`);
} catch {
// @nestjs/swagger not installed - skip silently
}
}
/**
* Bootstrap a NestJS application with standard configuration
*
@ -105,6 +153,7 @@ export function configurePrefix(
* defaultPort: 3002,
* serviceName: 'Chat',
* additionalCorsOrigins: ['http://localhost:5178'],
* swagger: true,
* });
* ```
*/
@ -135,8 +184,13 @@ export async function bootstrapApp(
options.excludeFromPrefix ?? ['metrics', 'health']
);
// Start listening
// Setup Swagger/OpenAPI docs if enabled
const port = process.env.PORT || options.defaultPort;
if (options.swagger) {
await setupSwagger(app, options.serviceName, port, options.swagger);
}
// Start listening
await app.listen(port);
console.log(`${options.serviceName} backend running on http://localhost:${port}`);

18
pnpm-lock.yaml generated
View file

@ -467,6 +467,9 @@ importers:
'@nestjs/platform-express':
specifier: ^10.4.15
version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)
'@nestjs/throttler':
specifier: ^6.2.1
version: 6.4.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)(reflect-metadata@0.2.2)
class-transformer:
specifier: ^0.5.1
version: 0.5.1
@ -1300,6 +1303,9 @@ importers:
'@manacore/shared-vite-config':
specifier: workspace:*
version: link:../../../../packages/shared-vite-config
'@playwright/test':
specifier: ^1.52.0
version: 1.57.0
'@sveltejs/adapter-node':
specifier: ^5.0.0
version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)))
@ -2726,6 +2732,9 @@ importers:
'@nestjs/platform-express':
specifier: ^10.4.15
version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)
'@nestjs/throttler':
specifier: ^6.2.1
version: 6.4.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)(reflect-metadata@0.2.2)
class-transformer:
specifier: ^0.5.1
version: 0.5.1
@ -3487,6 +3496,9 @@ importers:
'@nestjs/platform-express':
specifier: ^10.4.15
version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)
'@nestjs/throttler':
specifier: ^6.2.1
version: 6.4.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)(reflect-metadata@0.2.2)
class-transformer:
specifier: ^0.5.1
version: 0.5.1
@ -5324,6 +5336,9 @@ importers:
'@manacore/shared-vite-config':
specifier: workspace:*
version: link:../../../../packages/shared-vite-config
'@playwright/test':
specifier: ^1.52.0
version: 1.57.0
'@sveltejs/adapter-node':
specifier: ^5.0.0
version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)))
@ -6423,6 +6438,9 @@ importers:
'@nestjs/core':
specifier: ^10.0.0 || ^11.0.0
version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/swagger':
specifier: ^8.0.0 || ^7.0.0
version: 8.1.1(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)
express:
specifier: ^4.21.0
version: 4.21.2