Node.js Best Practices Cho Backend Developer
Tổng hợp các best practices khi phát triển Node.js: cấu trúc project, error handling, async, security và performance.
Node.js chạy hơn 50% ứng dụng web backend. Nhưng dễ viết, khó làm đúng. Bài này tổng hợp những best practices thực tế.
1. Cấu Trúc Project — Folder-by-Feature#
src/
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── auth.routes.ts
│ │ └── auth.test.ts
│ └── user/
│ ├── user.controller.ts
│ ├── user.service.ts
│ ├── user.repository.ts
│ └── user.test.ts
├── shared/
│ ├── middleware/
│ ├── utils/
│ └── types/
├── config/
│ └── env.ts
└── app.tsplaintextFolder-by-layer (controllers/, services/, models/) lộn xộn khi project lớn. Folder-by-feature — mỗi module tự quản lý route, controller, service, test riêng.
2. Error Handling — Không Để Crash#
Custom Error Class#
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public code?: string
) {
super(message);
this.name = 'AppError';
}
}
// Usage
throw new AppError(404, 'User not found', 'USER_NOT_FOUND');typescriptGlobal Error Handler#
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: { code: err.code, message: err.message }
});
}
// Lỗi không mong đợi — log và trả về 500
console.error('Unexpected error:', err);
res.status(500).json({
error: { message: 'Internal server error' }
});
});typescript3. Async — Luôn Dùng try/catch Hoặc Promise.catch#
// ❌ Tệ — crash nếu reject
app.get('/users/:id', async (req, res) => {
const user = await userService.findById(req.params.id);
res.json(user);
});
// ✅ Tốt — wrapper
const asyncHandler = (fn: Function) =>
(req: Request, res: Response, next: NextFunction) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await userService.findById(req.params.id);
res.json(user);
}));typescript4. Environment Variables — Zod Validation#
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['dev', 'prod', 'test']).default('dev'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional(),
});
export const env = envSchema.parse(process.env);
// Throw ngay khi start nếu thiếu biến — không đợi runtimetypescript5. Logging — Có Cấu Trúc#
import pino from 'pino';
const logger = pino({
level: env.NODE_ENV === 'prod' ? 'info' : 'debug',
transport: env.NODE_ENV === 'dev'
? { target: 'pino-pretty' }
: undefined,
});
logger.info({ userId, action: 'login' }, 'User logged in');
logger.error({ err, orderId }, 'Failed to process order');typescriptKhông dùng console.log — nó không có level, không structured, không thể search trong production.
6. Security Headers#
import helmet from 'helmet';
app.use(helmet());
// Tự động thêm: CSP, X-Frame-Options, X-Content-Type-Options, HSTS...typescript7. Graceful Shutdown#
process.on('SIGTERM', async () => {
logger.info('SIGTERM received. Shutting down gracefully...');
server.close(async () => {
await db.disconnect();
await redis.disconnect();
process.exit(0);
});
// Force exit sau 30s
setTimeout(() => {
logger.error('Forced shutdown');
process.exit(1);
}, 30_000);
});typescript8. Package.json — Script Hữu Ích#
{
"scripts": {
"dev": "tsx watch src/app.ts",
"build": "tsc",
"start": "node dist/app.js",
"test": "vitest",
"test:cov": "vitest --coverage",
"lint": "eslint src/",
"typecheck": "tsc --noEmit",
"format": "prettier --write src/"
}
}json9. Health Check Endpoint#
app.get('/health', async (req, res) => {
const dbHealthy = await db.$queryRaw`SELECT 1`;
const redisHealthy = await redis.ping();
res.json({
status: dbHealthy && redisHealthy ? 'healthy' : 'degraded',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
});
});typescript10. Dependencies — Luôn Cập Nhật#
# Kiểm tra outdated
npm outdated
pnpm outdated
# Cập nhật major
npx npm-check-updates -u
npm installbashKết Luận#
Node.js dễ bắt đầu nhưng production đòi hỏi kỷ luật. Những practices trên giúp bạn:
- Code dễ đọc, dễ mở rộng
- Hạn chế crash không mong muốn
- Dễ debug và monitoring
- An toàn hơn trong production