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:
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@@ -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'}
|
||||
|
||||
72
services/_template/ARCHITECTURE.md
Normal file
72
services/_template/ARCHITECTURE.md
Normal 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*)
|
||||
@@ -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`.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
70
services/_template/src/middlewares/metrics.middleware.ts
Normal file
70
services/_template/src/middlewares/metrics.middleware.ts
Normal 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();
|
||||
};
|
||||
@@ -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: {
|
||||
|
||||
@@ -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' },
|
||||
|
||||
35
services/_template/src/modules/metrics/metrics.controller.ts
Normal file
35
services/_template/src/modules/metrics/metrics.controller.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user