import { logger } from '@goodgo/logger'; import { initTracing } from '@goodgo/tracing'; import cors from 'cors'; import express from 'express'; import rateLimit from 'express-rate-limit'; import helmet from 'helmet'; import { RedisStore } from 'rate-limit-redis'; import { appConfig } from './config/app.config'; import { connectDatabase } from './config/database.config'; import { prisma } from './config/database.config'; import { getRedisClient } from './config/redis.config'; import { setupSwagger } from './docs/swagger'; import { correlationMiddleware } from './middlewares/correlation.middleware'; import { errorHandler, notFoundHandler } from './middlewares/error.middleware'; import { requestLogger } from './middlewares/logger.middleware'; import { metricsMiddleware } from './middlewares/metrics.middleware'; import { createRouter } from './routes'; // EN: Initialize tracing // VI: Khởi tạo tracing if (process.env.TRACING_ENABLED === 'true') { initTracing({ serviceName: process.env.SERVICE_NAME || 'microservice', otlpEndpoint: process.env.OTLP_ENDPOINT, enabled: true, }); } const app = express(); // EN: Security middleware // VI: Middleware bảo mật app.use(helmet()); app.use( cors({ origin: appConfig.corsOrigin, credentials: true, }) ); // EN: Rate limiting // VI: Giới hạn số lượng request const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, // EN: Use Redis for distributed rate limiting // VI: Sử dụng Redis để giới hạn rate phân tán store: new RedisStore({ // @ts-expect-error - rate-limit-redis types mismatch with ioredis sendCommand: (...args: string[]) => getRedisClient().call(...args), }), }); app.use('/api', limiter); // EN: Correlation ID middleware (must be early) // VI: Correlation ID middleware (phải đặt sớm) app.use(correlationMiddleware()); // EN: Body parsing // VI: Phân tích body request app.use(express.json()); app.use(express.urlencoded({ extended: true })); // EN: Request logging // VI: Ghi log request app.use(requestLogger); // EN: Metrics // VI: Metrics app.use(metricsMiddleware); // EN: Routes with async error handling // VI: Routes với async error handling app.use(createRouter()); // EN: Setup Swagger documentation // VI: Thiết lập tài liệu Swagger setupSwagger(app, '/api-docs'); // EN: Error handling // VI: Xử lý lỗi app.use(notFoundHandler); app.use(errorHandler); const startServer = async () => { try { await connectDatabase(); const server = app.listen(appConfig.port, () => { logger.info(`Service started on port ${appConfig.port}`, { port: appConfig.port, nodeEnv: appConfig.nodeEnv, }); }); // EN: Graceful shutdown // VI: Đóng ứng dụng một cách an toàn const shutdown = async (signal: string) => { logger.info(`${signal} received, shutting down gracefully`); server.close(async () => { logger.info('HTTP server closed'); try { await prisma.$disconnect(); logger.info('Database connection closed'); process.exit(0); } catch (error) { logger.error('Error during shutdown', { error }); process.exit(1); } }); // EN: Force shutdown after 10s // VI: Buộc dừng sau 10 giây setTimeout(() => { logger.error('Forcing shutdown after timeout'); process.exit(1); }, 10000); }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); } catch (error) { logger.error('Failed to start server', { error }); process.exit(1); } }; startServer();