require('dotenv').config(); const express = require('express'); const cors = require('cors'); const morgan = require('morgan'); const swaggerUi = require('swagger-ui-express'); const swaggerSpec = require('./config/swagger'); const logger = require('./config/logger'); const { errorHandler, notFoundHandler } = require('./middleware/errorHandler'); const { testConnection, getDatabaseStats } = require('./config/db'); const { validateEnvironment } = require('./validate-env'); const { isRedisConnected } = require('./config/redis'); // Security middleware const { helmetConfig, customSecurityHeaders, getCorsOptions } = require('./middleware/security'); const { sanitizeAll } = require('./middleware/sanitization'); const { apiLimiter, docsLimiter } = require('./middleware/rateLimiter'); // Validate environment configuration on startup console.log('\nšŸ”§ Validating environment configuration...'); const isEnvValid = validateEnvironment(); if (!isEnvValid) { console.error('āŒ Environment validation failed. Please fix errors and restart.'); process.exit(1); } const app = express(); // Configuration const config = require('./config/config'); const PORT = config.server.port; const API_PREFIX = config.server.apiPrefix; const NODE_ENV = config.server.nodeEnv; // Trust proxy - important for rate limiting and getting real client IP app.set('trust proxy', 1); // Security middleware - order matters! // 1. Helmet for security headers app.use(helmetConfig); // 2. Custom security headers app.use(customSecurityHeaders); // 3. CORS configuration app.use(cors(getCorsOptions())); // 4. Body parser middleware app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // 5. Input sanitization (NoSQL injection, XSS, HPP) app.use(sanitizeAll); // 6. Logging middleware if (NODE_ENV === 'development') { app.use(morgan('dev', { stream: logger.stream })); } else { app.use(morgan('combined', { stream: logger.stream })); } // 7. Log all requests in development if (NODE_ENV === 'development') { app.use((req, res, next) => { logger.info(`${req.method} ${req.originalUrl}`, { ip: req.ip, userAgent: req.get('user-agent') }); next(); }); } // 8. Global rate limiting for all API routes app.use(API_PREFIX, apiLimiter); // API Documentation - with rate limiting app.use('/api-docs', docsLimiter, swaggerUi.serve, swaggerUi.setup(swaggerSpec, { customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'Interview Quiz API Documentation', customfavIcon: '/favicon.ico' })); // Swagger JSON endpoint - with rate limiting app.get('/api-docs.json', docsLimiter, (req, res) => { res.setHeader('Content-Type', 'application/json'); res.send(swaggerSpec); }); // Health check endpoint app.get('/health', async (req, res) => { const dbStats = await getDatabaseStats(); res.status(200).json({ status: 'OK', message: 'Interview Quiz API is running', timestamp: new Date().toISOString(), environment: NODE_ENV, database: dbStats }); }); // API routes const authRoutes = require('./routes/auth.routes'); const guestRoutes = require('./routes/guest.routes'); const categoryRoutes = require('./routes/category.routes'); const questionRoutes = require('./routes/question.routes'); const adminRoutes = require('./routes/admin.routes'); const quizRoutes = require('./routes/quiz.routes'); const userRoutes = require('./routes/user.routes'); app.use(`${API_PREFIX}/auth`, authRoutes); app.use(`${API_PREFIX}/guest`, guestRoutes); app.use(`${API_PREFIX}/categories`, categoryRoutes); app.use(`${API_PREFIX}/questions`, questionRoutes); app.use(`${API_PREFIX}/admin`, adminRoutes); app.use(`${API_PREFIX}/quiz`, quizRoutes); app.use(`${API_PREFIX}/users`, userRoutes); // Root endpoint app.get('/', (req, res) => { res.json({ message: 'Welcome to Interview Quiz API', version: '2.0.0', documentation: '/api-docs' }); }); // 404 handler - must be after all routes app.use(notFoundHandler); // Global error handler - must be last app.use(errorHandler); // Start server app.listen(PORT, async () => { logger.info('Server starting up...'); const redisStatus = isRedisConnected() ? 'āœ… Connected' : 'āš ļø Not Connected (Optional)'; console.log(` ╔════════════════════════════════════════╗ ā•‘ Interview Quiz API - MySQL Edition ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• šŸš€ Server running on port ${PORT} šŸŒ Environment: ${NODE_ENV} šŸ”— API Endpoint: http://localhost:${PORT}${API_PREFIX} šŸ“Š Health Check: http://localhost:${PORT}/health šŸ“š API Docs: http://localhost:${PORT}/api-docs šŸ“ Logs: backend/logs/ šŸ’¾ Cache (Redis): ${redisStatus} `); logger.info(`Server started successfully on port ${PORT}`); // Test database connection on startup console.log('šŸ”Œ Testing database connection...'); const connected = await testConnection(); if (!connected) { console.warn('āš ļø Warning: Database connection failed. Server is running but database operations will fail.'); } // Log Redis status if (isRedisConnected()) { console.log('šŸ’¾ Redis cache connected and ready'); logger.info('Redis cache connected'); } else { console.log('āš ļø Redis not connected - caching disabled (optional feature)'); logger.warn('Redis not connected - caching disabled'); } }); // Handle unhandled promise rejections process.on('unhandledRejection', (err) => { console.error('Unhandled Promise Rejection:', err); // Close server & exit process process.exit(1); }); module.exports = app;