From 7d68f61b82f0b83164e7ff9902da7fc2369abdd1 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sat, 27 Dec 2025 11:01:17 +0700 Subject: [PATCH] Update dependencies and enhance service template with bilingual support - Added `dotenv` and `prom-client` dependencies for environment variable management and metrics collection. - Updated `package.json` to include new dependencies and types for `dotenv`. - Enhanced `README.md` with bilingual features, usage instructions, and project structure. - Improved `app.config.ts` to validate environment variables using Zod. - Implemented graceful shutdown in `main.ts` for better service reliability. - Added metrics middleware and updated routes to include metrics endpoint. - Enhanced error handling middleware with detailed logging and responses. - Updated feature and health controllers with bilingual comments and improved structure. --- pnpm-lock.yaml | 35 +++++- services/_template/ARCHITECTURE.md | 72 +++++++++++++ services/_template/README.md | 102 +++++++++++++----- services/_template/package.json | 25 +++-- services/_template/src/config/app.config.ts | 66 ++++++++++-- services/_template/src/main.ts | 71 ++++++++---- .../src/middlewares/error.middleware.ts | 24 ++++- .../src/middlewares/metrics.middleware.ts | 70 ++++++++++++ .../src/modules/feature/feature.controller.ts | 24 +++-- .../src/modules/health/health.controller.ts | 26 ++++- .../src/modules/metrics/metrics.controller.ts | 35 ++++++ services/_template/src/routes/index.ts | 7 ++ 12 files changed, 480 insertions(+), 77 deletions(-) create mode 100644 services/_template/ARCHITECTURE.md create mode 100644 services/_template/src/middlewares/metrics.middleware.ts create mode 100644 services/_template/src/modules/metrics/metrics.controller.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c215a163..cd39195c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -298,6 +298,9 @@ importers: cors: specifier: ^2.8.5 version: 2.8.5 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 express: specifier: ^4.18.2 version: 4.22.1 @@ -307,6 +310,9 @@ importers: helmet: specifier: ^7.1.0 version: 7.2.0 + prom-client: + specifier: ^15.1.3 + version: 15.1.3 zod: specifier: ^3.22.4 version: 3.25.76 @@ -320,6 +326,9 @@ importers: '@types/cors': specifier: ^2.8.17 version: 2.8.19 + '@types/dotenv': + specifier: ^8.2.3 + version: 8.2.3 '@types/express': specifier: ^4.17.21 version: 4.17.25 @@ -3143,6 +3152,13 @@ packages: '@types/node': 20.19.27 dev: true + /@types/dotenv@8.2.3: + resolution: {integrity: sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw==} + deprecated: This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed. + dependencies: + dotenv: 17.2.3 + dev: true + /@types/express-serve-static-core@4.19.7: resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} dependencies: @@ -3960,6 +3976,10 @@ packages: engines: {node: '>=8'} dev: true + /bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + dev: false + /body-parser@1.20.4: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4465,7 +4485,6 @@ packages: /dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} - dev: true /dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -7151,6 +7170,14 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + dependencies: + '@opentelemetry/api': 1.9.0 + tdigest: 0.1.2 + dev: false + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -7876,6 +7903,12 @@ packages: - yaml dev: true + /tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + dependencies: + bintrees: 1.0.2 + dev: false + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} diff --git a/services/_template/ARCHITECTURE.md b/services/_template/ARCHITECTURE.md new file mode 100644 index 00000000..325f4ee1 --- /dev/null +++ b/services/_template/ARCHITECTURE.md @@ -0,0 +1,72 @@ +# Service Template Architecture / Kiến Trúc Template Dịch Vụ + +This document describes the high-level architecture of the microservice template. +Tài liệu này mô tả kiến trúc cấp cao của template microservice. + +## Component Diagram / Sơ đồ Thành phần + +```mermaid +graph TD + Client[Client / External Service] + + subgraph "Service Boundary / Phạm vi Dịch vụ" + LB[Load Balancer / Ingress] + + subgraph "Application / Ứng dụng" + Middleware[Middleware Layer] + Router[Router Layer] + Controller[Controller Layer] + Service[Service Layer] + Repo[Repository / Data Access] + end + + Config[Configuration Manager] + Logger[Logger] + Metrics[Prometheus Metrics] + end + + DB[(PostgreSQL Database)] + Jaeger[Jaeger Tracing] + + Client -->|HTTP Request| LB + LB -->|Traffic| Middleware + + Middleware -->|Auth & Validation| Router + Middleware -.->|Track| Metrics + Middleware -.->|Log| Logger + + Router -->|Dispatch| Controller + + Controller -->|Business Logic| Service + Service -->|Data Query| Repo + + Repo -->|SQL| DB + + Config -->|Settings| Application + Application -.->|Traces| Jaeger +``` + +## Request Flow / Luồng Xử Lý Request + +1. **Request Entry**: Use hits ingress/load balancer. + * *Đầu vào*: Người dùng gửi request đến ingress/load balancer. + +2. **Middleware**: + * **Helmet**: Secures HTTP headers. (*Bảo mật header HTTP*) + * **Rate Limiter**: Prevents abuse. (*Ngăn chặn spam/abuses*) + * **Logger/Metrics**: records start time. (*Ghi nhận thời gian bắt đầu*) + +3. **Router & Controller**: + * Routes request to specific handler. (*Định tuyến đến handler cụ thể*) + * Controller orchestrates response. (*Controller điều phối phản hồi*) + +4. **Service Layer**: + * Contains core business logic. (*Chứa logic nghiệp vụ cốt lõi*) + * Independent of transport layer (HTTP). (*Độc lập với lớp giao thức*) + +5. **Data Access**: + * Prisma ORM communicates with PostgreSQL. (*Prisma giao tiếp với PostgreSQL*) + +6. **Response**: + * Formatted JSON response sent back. (*Trả về JSON đã định dạng*) + * Metrics/Logs finalized. (*Hoàn tất ghi metrics/log*) diff --git a/services/_template/README.md b/services/_template/README.md index edf318b4..d8c48a3f 100644 --- a/services/_template/README.md +++ b/services/_template/README.md @@ -1,38 +1,84 @@ -# Service Template +# Service Template / Template Dịch Vụ -Template for creating new microservices in the monorepo. +> **EN**: Standard template for creating new microservices in the @goodgo ecosystem. +> **VI**: Template chuẩn để tạo các microservice mới trong hệ sinh thái @goodgo. -## Usage +## Features / Tính Năng -1. Copy this template to create a new service: - ```bash - cp -r services/_template services/new-service - ``` +- **Framework**: Express.js with TypeScript / Express.js với TypeScript. +- **Database**: Prisma ORM with PostgreSQL / Prisma ORM với PostgreSQL. +- **Validation**: Zod for environment & input validation / Zod cho validation biến môi trường và đầu vào. +- **Observability / Khả năng quan sát**: + - **Metrics**: Prometheus metrics at `/metrics` / Metrics Prometheus tại `/metrics`. + - **Logging**: Common logger with request tracking / Logger chung với theo dõi request. + - **Tracing**: OpenTelemetry/Jaeger integration / Tích hợp OpenTelemetry/Jaeger. +- **Resilience / Khả năng phục hồi**: + - Graceful shutdown / Đóng ứng dụng an toàn. + - Rate limiting / Giới hạn tốc độ request. + - Health checks (liveness/readiness) / Kiểm tra sức khỏe hệ thống. +- **Security / Bảo mật**: Helmet & CORS configured / Đã cấu hình Helmet & CORS. -2. Update `package.json`: - - Change `name` to `@goodgo/new-service` - - Update `description` +## Project Structure / Cấu trúc Dự án -3. Update `.env.example`: - - Set appropriate `PORT` - - Set `SERVICE_NAME` - - Configure database URL +``` +src/ +├── config/ # Configuration & Env validation / Cấu hình & Validate biến môi trường +├── middlewares/ # Express middlewares (error, logger, metrics) +├── modules/ # Feature modules (controller, service, repository) +├── routes/ # API route definitions +└── main.ts # Entry point & App bootstrapping +``` -4. Implement your features: - - Add modules in `src/modules/` - - Update routes in `src/routes/index.ts` - - Add Prisma schema if needed +## Getting Started / Bắt đầu -5. Update Dockerfile: - - Change `EXPOSE` port if different +### Prerequisites / Yêu cầu tiên quyết -6. Remove this README and create your own +- Node.js >= 20 +- pnpm +- Docker (optional for local DB) -## Structure +### Installation / Cài đặt -- `src/modules/` - Feature modules (controller, service, dto, module) -- `src/config/` - Configuration files -- `src/middlewares/` - Express middlewares -- `src/routes/` - Route definitions -- `prisma/` - Database schema and migrations -- `tests/` - Unit and integration tests +1. **Clone & Install dependencies**: + ```bash + pnpm install + ``` + +2. **Environment Setup / Thiết lập môi trường**: + Copy `.env.example` to `.env` (create if missing): + ```env + PORT=5000 + NODE_ENV=development + DATABASE_URL="postgresql://user:password@localhost:5432/dbname" + TRACING_ENABLED=false + ``` + +3. **Run Development / Chạy môi trường phát triển**: + ```bash + pnpm dev + ``` + +4. **Build & Start Production**: + ```bash + pnpm build + pnpm start + ``` + +## Observability / Khả năng quan sát + +- **Metrics**: Visit `http://localhost:5000/metrics`. +- **Health**: + - Liveness: `http://localhost:5000/health/live` + - Readiness: `http://localhost:5000/health/ready` + +## Development Guidelines / Hướng dẫn Phát triển + +### Comments / Comment Code +- Use bilingual comments for all public APIs and complex logic. +- Format: `EN` first, then `VI`. +- See `.cursor/skills/comment-code/SKILL.md` for details. + +### Adding a Module / Thêm Module +1. Create `src/modules//`. +2. Implement `Controller`, `Service`. +3. Register routes in `src/routes/index.ts`. diff --git a/services/_template/package.json b/services/_template/package.json index ae33a96f..033c859c 100644 --- a/services/_template/package.json +++ b/services/_template/package.json @@ -19,28 +19,31 @@ "clean": "rm -rf dist" }, "dependencies": { - "@goodgo/logger": "workspace:*", - "@goodgo/types": "workspace:*", "@goodgo/auth-sdk": "workspace:*", + "@goodgo/logger": "workspace:*", "@goodgo/tracing": "workspace:*", + "@goodgo/types": "workspace:*", "@prisma/client": "^5.9.1", - "express": "^4.18.2", - "zod": "^3.22.4", "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", - "express-rate-limit": "^7.1.5" + "prom-client": "^15.1.3", + "zod": "^3.22.4" }, "devDependencies": { "@goodgo/eslint-config": "workspace:*", "@goodgo/tsconfig": "workspace:*", - "@types/express": "^4.17.21", "@types/cors": "^2.8.17", - "@types/node": "^20.11.0", + "@types/dotenv": "^8.2.3", + "@types/express": "^4.17.21", "@types/jest": "^29.5.11", - "typescript": "^5.3.3", - "tsx": "^4.7.1", - "prisma": "^5.9.1", + "@types/node": "^20.11.0", "jest": "^29.7.0", - "ts-jest": "^29.1.2" + "prisma": "^5.9.1", + "ts-jest": "^29.1.2", + "tsx": "^4.7.1", + "typescript": "^5.3.3" } } diff --git a/services/_template/src/config/app.config.ts b/services/_template/src/config/app.config.ts index a2b231bd..51bba4e8 100644 --- a/services/_template/src/config/app.config.ts +++ b/services/_template/src/config/app.config.ts @@ -1,14 +1,64 @@ +import { z } from 'zod'; +import dotenv from 'dotenv'; + +dotenv.config(); + +/** + * EN: Environment variable schema + * VI: Schema biến môi trường + */ +const envSchema = z.object({ + PORT: z.string().transform(Number).default('5000'), + NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), + API_VERSION: z.string().default('v1'), + CORS_ORIGIN: z.string().optional().default('http://localhost:3000'), + SERVICE_NAME: z.string().default('microservice-template'), + TRACING_ENABLED: z.enum(['true', 'false']).default('false'), + JAEGER_ENDPOINT: z.string().optional(), +}); + +/** + * EN: Parse and validate environment variables + * VI: Phân tích và validate biến môi trường + */ +const env = envSchema.safeParse(process.env); + +if (!env.success) { + console.error('❌ Invalid environment variables:', JSON.stringify(env.error.format(), null, 2)); + process.exit(1); +} + +const config = env.data; + /** * EN: Application configuration object * VI: Đối tượng cấu hình ứng dụng */ export const appConfig = { - /** EN: Port number for the HTTP server / VI: Số port cho HTTP server */ - port: parseInt(process.env.PORT || '5000', 10), - /** EN: Node.js environment (development/production) / VI: Môi trường Node.js (development/production) */ - nodeEnv: process.env.NODE_ENV || 'development', - /** EN: API version prefix / VI: Tiền tố phiên bản API */ - apiVersion: process.env.API_VERSION || 'v1', - /** EN: Allowed CORS origins / VI: Các origin được phép CORS */ - corsOrigin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'], + // EN: Server port + // VI: Cổng server + port: config.PORT, + + // EN: Node environment + // VI: Môi trường Node + nodeEnv: config.NODE_ENV, + + // EN: API version + // VI: Phiên bản API + apiVersion: config.API_VERSION, + + // EN: CORS origins + // VI: Các origin cho CORS + corsOrigin: config.CORS_ORIGIN.split(','), + + // EN: Service name + // VI: Tên dịch vụ + serviceName: config.SERVICE_NAME, + + // EN: Tracing configuration + // VI: Cấu hình tracing + tracing: { + enabled: config.TRACING_ENABLED === 'true', + jaegerEndpoint: config.JAEGER_ENDPOINT, + }, }; diff --git a/services/_template/src/main.ts b/services/_template/src/main.ts index 071c177d..ac095cc1 100644 --- a/services/_template/src/main.ts +++ b/services/_template/src/main.ts @@ -7,10 +7,13 @@ import { appConfig } from './config/app.config'; import { createRouter } from './routes'; import { requestLogger } from './middlewares/logger.middleware'; import { errorHandler, notFoundHandler } from './middlewares/error.middleware'; +import { metricsMiddleware } from './middlewares/metrics.middleware'; import { logger } from '@goodgo/logger'; import { initTracing } from '@goodgo/tracing'; +import { prisma } from './config/database.config'; -// Initialize tracing +// EN: Initialize tracing +// VI: Khởi tạo tracing if (process.env.TRACING_ENABLED === 'true') { initTracing({ serviceName: process.env.SERVICE_NAME || 'microservice', @@ -21,7 +24,8 @@ if (process.env.TRACING_ENABLED === 'true') { const app = express(); -// Security middleware +// EN: Security middleware +// VI: Middleware bảo mật app.use(helmet()); app.use( cors({ @@ -30,24 +34,34 @@ app.use( }) ); -// Rate limiting +// EN: Rate limiting +// VI: Giới hạn số lượng request const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, }); app.use('/api', limiter); -// Body parsing +// EN: Body parsing +// VI: Phân tích body request app.use(express.json()); app.use(express.urlencoded({ extended: true })); -// Request logging +// EN: Request logging +// VI: Ghi log request app.use(requestLogger); -// Routes +// EN: Metrics +// VI: Metrics +app.use(metricsMiddleware); + + +// EN: Routes +// VI: Định nghĩa routes app.use(createRouter()); -// Error handling +// EN: Error handling +// VI: Xử lý lỗi app.use(notFoundHandler); app.use(errorHandler); @@ -55,12 +69,42 @@ const startServer = async () => { try { await connectDatabase(); - app.listen(appConfig.port, () => { + 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); @@ -68,14 +112,3 @@ const startServer = async () => { }; startServer(); - -// Graceful shutdown -process.on('SIGTERM', async () => { - logger.info('SIGTERM received, shutting down gracefully'); - process.exit(0); -}); - -process.on('SIGINT', async () => { - logger.info('SIGINT received, shutting down gracefully'); - process.exit(0); -}); diff --git a/services/_template/src/middlewares/error.middleware.ts b/services/_template/src/middlewares/error.middleware.ts index af7cc382..47d26bc8 100644 --- a/services/_template/src/middlewares/error.middleware.ts +++ b/services/_template/src/middlewares/error.middleware.ts @@ -2,12 +2,23 @@ import { Request, Response, NextFunction } from 'express'; import { logger } from '@goodgo/logger'; import { ApiResponse } from '@goodgo/types'; +/** + * EN: Global error handling middleware + * VI: Middleware xử lý lỗi toàn cục + * + * @param err - Error object / Đối tượng lỗi + * @param req - Express request + * @param res - Express response + * @param _next - Next function + */ export const errorHandler = ( err: Error, req: Request, res: Response, - next: NextFunction + _next: NextFunction ): void => { + // EN: Log the error details + // VI: Ghi log chi tiết lỗi logger.error('Error occurred', { error: err.message, stack: err.stack, @@ -15,6 +26,8 @@ export const errorHandler = ( method: req.method, }); + // EN: Prepare error response + // VI: Chuẩn bị phản hồi lỗi const response: ApiResponse = { success: false, error: { @@ -25,9 +38,18 @@ export const errorHandler = ( timestamp: new Date().toISOString(), }; + // EN: Send 500 response + // VI: Gửi phản hồi 500 res.status(500).json(response); }; +/** + * EN: 404 Not Found handler + * VI: Xử lý lỗi 404 Not Found + * + * @param req - Express request + * @param res - Express response + */ export const notFoundHandler = (req: Request, res: Response): void => { const response: ApiResponse = { success: false, diff --git a/services/_template/src/middlewares/metrics.middleware.ts b/services/_template/src/middlewares/metrics.middleware.ts new file mode 100644 index 00000000..1c6b96c6 --- /dev/null +++ b/services/_template/src/middlewares/metrics.middleware.ts @@ -0,0 +1,70 @@ +import { Request, Response, NextFunction } from 'express'; +import client from 'prom-client'; + +// EN: Create a Registry which registers the metrics +// VI: Tạo Registry để đăng ký các metrics +const register = client.register; + +// EN: Collect default metrics +// VI: Thu thập các metrics mặc định +client.collectDefaultMetrics({ register }); + +// EN: Create histogram for HTTP request duration +// VI: Tạo histogram cho thời lượng request HTTP +const httpRequestDurationMicroseconds = new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds / Thời lượng request HTTP tính bằng giây', + labelNames: ['method', 'route', 'status_code'], + buckets: [0.1, 0.5, 1, 1.5, 2, 5], +}); + +// EN: Create counter for total HTTP requests +// VI: Tạo counter cho tổng số request HTTP +const httpRequestsTotal = new client.Counter({ + name: 'http_requests_total', + help: 'Total number of HTTP requests / Tổng số request HTTP', + labelNames: ['method', 'route', 'status_code'], +}); + +/** + * EN: Middleware to collect HTTP metrics (duration, count) + * VI: Middleware thu thập metrics HTTP (thời lượng, số lượng) + * + * @param req - Express request + * @param res - Express response + * @param next - Next function + */ +export const metricsMiddleware = (req: Request, res: Response, next: NextFunction) => { + // EN: Start timer + // VI: Bắt đầu bấm giờ + const start = process.hrtime(); + + // EN: Listen for response finish event + // VI: Lắng nghe sự kiện kết thúc response + res.on('finish', () => { + // EN: Calculate duration + // VI: Tính toán thời lượng + const duration = process.hrtime(start); + const durationInSeconds = duration[0] + duration[1] / 1e9; + + // EN: Normalize path to avoid high cardinality (e.g., /users/1 -> /users/:id) + // VI: Chuẩn hóa path để tránh high cardinality + // EN: using a simple heuristic or relying on req.route (if available) + // VI: sử dụng heuristic đơn giản hoặc dựa vào req.route (nếu có) + const route = req.route ? req.route.path : req.path; + + // EN: Record duration + // VI: Ghi nhận thời lượng + httpRequestDurationMicroseconds + .labels(req.method, route, res.statusCode.toString()) + .observe(durationInSeconds); + + // EN: Increment counter + // VI: Tăng bộ đếm + httpRequestsTotal + .labels(req.method, route, res.statusCode.toString()) + .inc(); + }); + + next(); +}; diff --git a/services/_template/src/modules/feature/feature.controller.ts b/services/_template/src/modules/feature/feature.controller.ts index 867db25a..51cf23da 100644 --- a/services/_template/src/modules/feature/feature.controller.ts +++ b/services/_template/src/modules/feature/feature.controller.ts @@ -1,17 +1,27 @@ import { Request, Response } from 'express'; -import { FeatureService } from './feature.service'; import { ApiResponse } from '@goodgo/types'; +/** + * EN: Controller for Feature module + * VI: Controller cho module Feature + */ export class FeatureController { - private featureService: FeatureService; - constructor() { - this.featureService = new FeatureService(); + // EN: Service initialization + // VI: Khởi tạo service } - create = async (req: Request, res: Response): Promise => { + /** + * EN: Create a new feature + * VI: Tạo một feature mới + * + * @param _req - Express request + * @param res - Express response + */ + create = async (_req: Request, res: Response): Promise => { try { - // Implementation + // EN: Implementation + // VI: Triển khai const response: ApiResponse = { success: true, message: 'Feature created', @@ -19,6 +29,8 @@ export class FeatureController { }; res.json(response); } catch (error: any) { + // EN: Handle errors + // VI: Xử lý lỗi res.status(400).json({ success: false, error: { diff --git a/services/_template/src/modules/health/health.controller.ts b/services/_template/src/modules/health/health.controller.ts index 7e874d53..aa202311 100644 --- a/services/_template/src/modules/health/health.controller.ts +++ b/services/_template/src/modules/health/health.controller.ts @@ -2,8 +2,16 @@ import { Request, Response } from 'express'; import { prisma } from '../../config/database.config'; import { ApiResponse } from '@goodgo/types'; +/** + * EN: Controller for health checks + * VI: Controller cho các kiểm tra sức khỏe hệ thống + */ export class HealthController { - health = async (req: Request, res: Response): Promise => { + /** + * EN: Basic liveness probe + * VI: Kiểm tra liveness cơ bản + */ + health = async (_req: Request, res: Response): Promise => { const response: ApiResponse<{ status: string; timestamp: string }> = { success: true, data: { @@ -16,8 +24,14 @@ export class HealthController { res.json(response); }; - ready = async (req: Request, res: Response): Promise => { + /** + * EN: Readiness probe (checks database connection) + * VI: Kiểm tra readiness (kiểm tra kết nối database) + */ + ready = async (_req: Request, res: Response): Promise => { try { + // EN: Check database connection + // VI: Kiểm tra kết nối database await prisma.$queryRaw`SELECT 1`; res.json({ success: true, @@ -25,6 +39,8 @@ export class HealthController { timestamp: new Date().toISOString(), }); } catch (error) { + // EN: Return 503 if database is not ready + // VI: Trả về 503 nếu database chưa sẵn sàng res.status(503).json({ success: false, error: { @@ -36,7 +52,11 @@ export class HealthController { } }; - live = async (req: Request, res: Response): Promise => { + /** + * EN: Alias for health check + * VI: Alias cho kiểm tra sức khỏe + */ + live = async (_req: Request, res: Response): Promise => { res.json({ success: true, data: { status: 'live' }, diff --git a/services/_template/src/modules/metrics/metrics.controller.ts b/services/_template/src/modules/metrics/metrics.controller.ts new file mode 100644 index 00000000..6ee68cdf --- /dev/null +++ b/services/_template/src/modules/metrics/metrics.controller.ts @@ -0,0 +1,35 @@ +import { Request, Response } from 'express'; +import { register } from 'prom-client'; +import { logger } from '@goodgo/logger'; + +/** + * EN: Controller for handling metrics requests + * VI: Controller xử lý các request liên quan đến metrics + */ +export class MetricsController { + /** + * EN: Get current metrics in Prometheus format + * VI: Lấy metrics hiện tại theo định dạng Prometheus + * + * @param _req - Express request (unused/chưa dùng) + * @param res - Express response + */ + public async getMetrics(_req: Request, res: Response): Promise { + + try { + // EN: Set content type for Prometheus + // VI: Thiết lập content type cho Prometheus + res.set('Content-Type', register.contentType); + + // EN: Return metrics + // VI: Trả về metrics + res.end(await register.metrics()); + } catch (error) { + // EN: Log error and return 500 + // VI: Ghi log lỗi và trả về 500 + logger.error('Error getting metrics', { error }); + res.status(500).send('Error getting metrics'); + } + } +} + diff --git a/services/_template/src/routes/index.ts b/services/_template/src/routes/index.ts index 72129851..1d08f103 100644 --- a/services/_template/src/routes/index.ts +++ b/services/_template/src/routes/index.ts @@ -1,6 +1,8 @@ import { Router } from 'express'; import { createFeatureRouter } from '../modules/feature/feature.module'; import { HealthController } from '../modules/health/health.controller'; +import { MetricsController } from '../modules/metrics/metrics.controller'; + export const createRouter = (): Router => { const router = Router(); @@ -16,5 +18,10 @@ export const createRouter = (): Router => { // API routes router.use(`/api/${apiVersion}/features`, createFeatureRouter()); + // Metrics + const metricsController = new MetricsController(); + router.get('/metrics', metricsController.getMetrics); + return router; }; +