import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { LoggerService } from '@modules/shared'; import cookieParser from 'cookie-parser'; import helmet from 'helmet'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true }); const logger = app.get(LoggerService); app.useLogger(logger); // ── OpenAPI / Swagger ── const swaggerConfig = new DocumentBuilder() .setTitle('Goodgo Platform API') .setDescription('Real-estate platform API — listings, search, payments, subscriptions, analytics') .setVersion('1.0') .addBearerAuth( { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'JWT', ) .addTag('auth', 'Authentication & user profile') .addTag('listings', 'Property listings CRUD & moderation') .addTag('search', 'Full-text & geo search') .addTag('payments', 'Payment processing & callbacks') .addTag('subscriptions', 'Plans, billing & usage metering') .addTag('admin', 'Admin panel operations') .addTag('notifications', 'Notification history & preferences') .addTag('analytics', 'Market reports & price analytics') .build(); const document = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('api/docs', app, document, { swaggerOptions: { persistAuthorization: true }, }); // ── Security Headers (Helmet) ── app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:', 'blob:'], connectSrc: ["'self'"], fontSrc: ["'self'", 'data:'], objectSrc: ["'none'"], frameSrc: ["'none'"], baseUri: ["'self'"], formAction: ["'self'"], }, }, crossOriginEmbedderPolicy: true, crossOriginOpenerPolicy: true, crossOriginResourcePolicy: { policy: 'same-origin' }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, }), ); // ── Cookie Parser (required for CSRF double-submit pattern) ── app.use(cookieParser()); // ── CORS ── const allowedOrigins = (process.env['CORS_ORIGINS'] ?? 'http://localhost:3000') .split(',') .map((o) => o.trim()); app.enableCors({ origin: allowedOrigins, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Correlation-Id', 'X-CSRF-Token'], exposedHeaders: ['X-Correlation-Id'], credentials: true, maxAge: 86400, }); // ── Global Validation Pipe ── app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true }, disableErrorMessages: process.env['NODE_ENV'] === 'production', }), ); // ── Request Body Size Limit ── // Express default is 100kb; explicitly set for clarity const expressApp = app.getHttpAdapter().getInstance(); const { json, urlencoded } = await import('express'); expressApp.use(json({ limit: '1mb' })); expressApp.use(urlencoded({ extended: true, limit: '1mb' })); // ── Trust Proxy (for rate limiting behind reverse proxy) ── expressApp.set('trust proxy', 1); const port = process.env['PORT'] ?? 3001; await app.listen(port); logger.log(`API running on http://localhost:${port}`, 'Bootstrap'); } bootstrap();