import { NestFactory } from '@nestjs/core'; import { ValidationPipe, Logger, BadRequestException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { NestExpressApplication } from '@nestjs/platform-express'; import helmet from 'helmet'; import cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; import { AllExceptionsFilter } from './common/filters'; import { TransformInterceptor } from './common/interceptors'; import { RedisIoAdapter } from './common/adapters/redis-io.adapter'; async function bootstrap() { const logger = new Logger('Bootstrap'); const app = await NestFactory.create(AppModule, { rawBody: true, }); const configService = app.get(ConfigService); const port = configService.get('app.port') || 3001; const appEnv = configService.get('app.env') || 'development'; const corsOrigins = configService.get('cors.origins') || []; // Security app.use(helmet()); app.use(cookieParser()); // CORS app.enableCors({ origin: corsOrigins, credentials: true, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], }); // Global prefix app.setGlobalPrefix('api/v1'); // Global pipes app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true, }, exceptionFactory: (errors) => { const messages = errors.map((error) => { const constraints = error.constraints ? Object.values(error.constraints) : [`${error.property} is invalid`]; return { field: error.property, errors: constraints, }; }); return new BadRequestException({ message: 'Validation failed', errors: messages, }); }, }), ); // Global filters app.useGlobalFilters(new AllExceptionsFilter()); // Global interceptors app.useGlobalInterceptors(new TransformInterceptor()); // Swagger documentation — enabled when NODE_ENV !== 'production' OR SWAGGER_ENABLED=true. // Lets us turn it on for beta (NODE_ENV=production) without exposing it in real prod. const swaggerEnabled = appEnv !== 'production' || process.env.SWAGGER_ENABLED === 'true'; if (swaggerEnabled) { const config = new DocumentBuilder() .setTitle('Real Estate Agent Platform API') .setDescription('API documentation for the Real Estate Agent Platform') .setVersion('1.0') .addBearerAuth( { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', name: 'JWT', description: 'Enter JWT token', in: 'header', }, 'JWT-auth', ) .addTag('Auth', 'Authentication endpoints') .addTag('Users', 'User management endpoints') .addTag('Agents', 'Agent management endpoints') .addTag('Health', 'Health check endpoints') .build(); const document = SwaggerModule.createDocument(app, config); // Mount at both /api/docs (existing) and /docs (cleaner subdomain URL). SwaggerModule.setup('api/docs', app, document, { swaggerOptions: { persistAuthorization: true }, }); SwaggerModule.setup('docs', app, document, { swaggerOptions: { persistAuthorization: true }, }); logger.log(`Swagger documentation available at /api/docs and /docs`); } // Socket.IO Redis adapter for cross-instance event broadcasting const redisIoAdapter = new RedisIoAdapter(app, configService); await redisIoAdapter.connectToRedis(); app.useWebSocketAdapter(redisIoAdapter); await app.listen(port); logger.log(`Application is running on: http://localhost:${port}`); logger.log(`Environment: ${appEnv}`); } bootstrap();