import { NestFactory } from '@nestjs/core'; import { ValidationPipe, VersioningType } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ConfigService } from '@nestjs/config'; import helmet from 'helmet'; import compression from 'compression'; import { AppModule } from './app.module'; import { Logger } from 'nestjs-pino'; import { helmetConfig, corsConfig } from './infrastructure/security/security.config'; import type { Request, Response, NextFunction } from 'express'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true, // Enable rawBody for Stripe webhooks signature verification rawBody: true, }); // Get config service const configService = app.get(ConfigService); const port = configService.get('PORT', 4000); const apiPrefix = configService.get('API_PREFIX', 'api/v1'); const isProduction = configService.get('NODE_ENV') === 'production'; // Use Pino logger app.useLogger(app.get(Logger)); // Security - Helmet with OWASP recommended headers app.use(helmet(helmetConfig)); // Compression for API responses app.use(compression()); // CORS with strict configuration app.enableCors(corsConfig); // Global prefix app.setGlobalPrefix(apiPrefix); // API versioning app.enableVersioning({ type: VersioningType.URI, }); // Global validation pipe app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true, }, }) ); // ─── Swagger documentation ──────────────────────────────────────────────── const swaggerUser = configService.get('SWAGGER_USERNAME'); const swaggerPass = configService.get('SWAGGER_PASSWORD'); const swaggerEnabled = !isProduction || (Boolean(swaggerUser) && Boolean(swaggerPass)); if (swaggerEnabled) { // HTTP Basic Auth guard for Swagger routes when credentials are configured if (swaggerUser && swaggerPass) { const swaggerPaths = ['/api/docs', '/api/docs-json', '/api/docs-yaml']; app.use(swaggerPaths, (req: Request, res: Response, next: NextFunction) => { const authHeader = req.headers['authorization']; if (!authHeader || !authHeader.startsWith('Basic ')) { res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"'); res.status(401).send('Authentication required'); return; } const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf-8'); const colonIndex = decoded.indexOf(':'); const user = decoded.slice(0, colonIndex); const pass = decoded.slice(colonIndex + 1); if (user !== swaggerUser || pass !== swaggerPass) { res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"'); res.status(401).send('Invalid credentials'); return; } next(); }); } const config = new DocumentBuilder() .setTitle('Xpeditis API') .setDescription( 'Maritime Freight Booking Platform - API for searching rates and managing bookings' ) .setVersion('1.0') .addBearerAuth() .addApiKey({ type: 'apiKey', name: 'x-api-key', in: 'header' }, 'x-api-key') .addTag('rates', 'Rate search and comparison') .addTag('bookings', 'Booking management') .addTag('auth', 'Authentication and authorization') .addTag('users', 'User management') .addTag('organizations', 'Organization management') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document, { customSiteTitle: 'Xpeditis API Documentation', customfavIcon: 'https://xpeditis.com/favicon.ico', customCss: '.swagger-ui .topbar { display: none }', }); } // ───────────────────────────────────────────────────────────────────────── await app.listen(port); const swaggerStatus = swaggerEnabled ? swaggerUser ? `http://localhost:${port}/api/docs (protected)` : `http://localhost:${port}/api/docs (open — add SWAGGER_USERNAME/PASSWORD to secure)` : 'disabled in production'; console.log(` ╔═══════════════════════════════════════════════╗ ║ ║ ║ 🚢 Xpeditis API Server Running ║ ║ ║ ║ API: http://localhost:${port}/${apiPrefix} ║ ║ Docs: ${swaggerStatus} ║ ║ ║ ╚═══════════════════════════════════════════════╝ `); } bootstrap();