Error Handling — Pattern Cho Ứng Dụng Sản Phẩm
Chiến lược error handling cho production: custom errors, Result type, recovery patterns và observability.
Error handling quyết định reliability của ứng dụng. Code không xử lý lỗi đúng cách là code không sẵn sàng cho production.
Error Types#
// 1. Operational Error — lỗi có thể dự đoán
// - Validation fails
// - Network timeout
// - Rate limit exceeded
// → Nên handle graceful
// 2. Programmer Error — bug
// - TypeError, ReferenceError
// - Logic sai
// → Nên crash hoặc restart
// 3. System Error — lỗi infrastructure
// - DB connection lost
// - Disk full
// → Nên retry hoặc failovertypescriptCustom Error Hierarchy#
// Base AppError
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public code?: string,
public details?: unknown,
) {
super(message);
this.name = this.constructor.name;
}
}
// Concrete errors
export class NotFoundError extends AppError {
constructor(resource: string, id: string | number) {
super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND');
}
}
export class ValidationError extends AppError {
constructor(errors: Record<string, string[]>) {
super('Validation failed', 400, 'VALIDATION_ERROR', errors);
}
}
export class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401, 'UNAUTHORIZED');
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 409, 'CONFLICT');
}
}typescriptResult Pattern — Không Try/Catch Tràn Lan#
// Discriminated union
type Result<T, E = AppError> =
| { success: true; data: T }
| { success: false; error: E };
async function findUser(id: number): Promise<Result<User>> {
try {
const user = await db.user.findUnique({ where: { id } });
if (!user) {
return { success: false, error: new NotFoundError('User', id) };
}
return { success: true, data: user };
} catch (err) {
return { success: false, error: new AppError('Database error') };
}
}
// Usage — pattern matching
const result = await findUser(1);
if (!result.success) {
// TypeScript biết đây là error case
console.error(result.error.message);
return response(404, { error: result.error.message });
}
// TypeScript biết đây là success case
console.log(result.data.name);typescriptExpress Error Handler#
// Controller wrapper
function asyncHandler(fn: Function) {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
// Routes
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await findUser(req.params.id);
res.json(user);
}));
// Global error handler (4 params)
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
// Log full error
logger.error({ err, path: req.path, method: req.method });
// Operational error
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
details: err.details,
},
});
}
// Programmer error — không leak detail
return res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'Something went wrong',
},
});
});typescriptRetry Pattern#
async function withRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
} = {},
): Promise<T> {
const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === maxRetries) throw err;
// Không retry lỗi validation
if (err instanceof ValidationError) throw err;
const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
const jitter = delay * (0.5 + Math.random() * 0.5);
await sleep(jitter);
}
}
throw new Error('Unreachable');
}
// Usage
const data = await withRetry(() => fetchExternalAPI(input), {
maxRetries: 3,
baseDelay: 500,
});typescriptCircuit Breaker#
class CircuitBreaker {
private failures = 0;
private lastFailureTime = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold: number = 5,
private resetTimeout: number = 30000,
) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime > this.resetTimeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await fn();
this.failures = 0;
this.state = 'closed';
return result;
} catch (err) {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
}
throw err;
}
}
}
// Usage
const breaker = new CircuitBreaker(5, 30000);
app.get('/api/external', async (req, res) => {
try {
const data = await breaker.call(() => externalService.fetch());
res.json(data);
} catch {
res.status(503).json({ error: 'Service temporarily unavailable' });
}
});typescriptGraceful Degradation#
Khi service phụ thuộc fail — app vẫn hoạt động (có thể giảm chức năng):
async function getUserProfile(userId: number) {
const user = await db.user.findUnique({ where: { id: userId } });
if (!user) throw new NotFoundError('User', userId);
// Recommendations là không critical
let recommendations: Recommendation[] = [];
try {
recommendations = await recommendationService.get(userId);
} catch (err) {
logger.warn({ err }, 'Recommendation service unavailable');
// Degrade gracefully — empty recommendations
}
return { ...user, recommendations };
}typescriptLogging Errors#
// Cấu trúc log chuẩn
logger.error({
err, // Error object
requestId: req.id,
userId: req.user?.id,
path: req.path,
method: req.method,
duration: Date.now() - start,
// Không log: password, token, secret
});typescriptMonitoring — Error Tracking#
// Error tracking (Sentry, Datadog, etc.)
import * as Sentry from '@sentry/node';
Sentry.init({ dsn: process.env.SENTRY_DSN });
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());
// Capture manually
try {
await processPayment();
} catch (err) {
Sentry.captureException(err, {
tags: { feature: 'payment' },
extra: { orderId, amount },
});
}typescriptChecklist#
- Custom error classes (AppError → NotFoundError, ValidationError…)
- Global error handler (không try/catch mỗi route)
- Retry với exponential backoff + jitter
- Circuit breaker cho external dependencies
- Graceful degradation khi service phụ fail
- Structured error log (không console.error)
- Error tracking (Sentry, Datadog)
- Không leak internal error detail ra response
Kết Luận#
Error handling là tính năng — không phì phụ. Result pattern cho type safety. Custom errors cho dễ debug. Retry + circuit breaker cho resilience. Graceful degradation cho user experience. Đầu tư vào error handling ngay từ đầu — không phải sau khi production crash.