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.
This commit is contained in:
Ho Ngoc Hai
2025-12-27 11:01:17 +07:00
parent d92cfb3bf3
commit 7d68f61b82
12 changed files with 480 additions and 77 deletions

35
pnpm-lock.yaml generated
View File

@@ -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'}

View File

@@ -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*)

View File

@@ -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/<name>/`.
2. Implement `Controller`, `Service`.
3. Register routes in `src/routes/index.ts`.

View File

@@ -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"
}
}

View File

@@ -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,
},
};

View File

@@ -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);
});

View File

@@ -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,

View File

@@ -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();
};

View File

@@ -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<void> => {
/**
* 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<void> => {
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: {

View File

@@ -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<void> => {
/**
* EN: Basic liveness probe
* VI: Kiểm tra liveness cơ bản
*/
health = async (_req: Request, res: Response): Promise<void> => {
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<void> => {
/**
* 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<void> => {
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<void> => {
/**
* EN: Alias for health check
* VI: Alias cho kiểm tra sức khỏe
*/
live = async (_req: Request, res: Response): Promise<void> => {
res.json({
success: true,
data: { status: 'live' },

View File

@@ -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<void> {
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');
}
}
}

View File

@@ -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;
};