Table of Contents
▼- Mengapa Error Handling Itu Krusial untuk Production
- Tingkatan Error dan Cara Menanganinya
- Membangun Error Response yang Konsisten
- Global Error Handler untuk Menangkap Semua Error
- Error Monitoring dan Alerting
- Circuit Breaker Pattern untuk External Services
- Graceful Degradation untuk User Experience
- Testing Error Handling
- Best Practices Error Handling di Production
- Kesimpulan
Pernahkah aplikasi Anda crash di tengah malam dan Anda baru tahu saat user komplain di WhatsApp? Atau error yang seharusnya mudah diperbaiki malah jadi mystery karena log-nya tidak jelas?
Error handling yang buruk bukan cuma bikin user frustasi, tapi juga bikin developer stres karena harus debugging tanpa informasi yang cukup.
Di artikel ini, kita akan bahas cara membangun error handling strategy yang robust untuk aplikasi production. Bukan sekadar try-catch asal-asalan, tapi sistem yang bisa mendeteksi, melaporkan, dan mengatasi error dengan efektif.
Mengapa Error Handling Itu Krusial untuk Production
Banyak developer pemula yang menganggap error handling sebagai "nice to have" atau sesuatu yang bisa ditambahkan nanti. Padahal, ini adalah fondasi aplikasi yang stabil.
Aplikasi tanpa error handling yang baik akan memberikan pengalaman yang buruk kepada user. Bayangkan user sedang checkout produk senilai jutaan rupiah, tiba-tiba muncul error "Something went wrong" tanpa penjelasan.
User tidak tahu apakah transaksi berhasil atau gagal. Mereka tidak tahu harus menghubungi siapa. Dan yang paling parah, mereka mungkin tidak akan kembali lagi.
Di sisi developer, error handling yang buruk membuat debugging menjadi nightmare. Anda harus menebak-nebak apa yang terjadi karena tidak ada informasi error yang cukup.
Tingkatan Error dan Cara Menanganinya
Tidak semua error harus ditangani dengan cara yang sama. Ada tingkatan error yang berbeda, dan masing-masing memerlukan strategi yang berbeda pula.
1. Expected Errors (Validation Errors)
Ini adalah error yang sudah kita prediksi akan terjadi. Contohnya: user memasukkan email dengan format salah, password terlalu pendek, atau field required yang kosong.
Expected errors tidak perlu logging yang kompleks. Cukup berikan feedback yang jelas kepada user agar mereka bisa memperbaiki input mereka.
// Contoh handling validation error di Laravel
public function store(Request $request)
{
try {
$validated = $request->validate([
'email' => 'required|email',
'password' => 'required|min:8',
'name' => 'required|max:255'
]);
// Process data...
} catch (ValidationException $e) {
return response()->json([
'message' => 'Data yang Anda masukkan tidak valid',
'errors' => $e->errors()
], 422);
}
}User-friendly error message adalah kunci. Jangan berikan pesan teknis seperti "SQLSTATE[23000]: Integrity constraint violation". Ubah menjadi "Email sudah terdaftar, silakan gunakan email lain."
2. Operational Errors (Recoverable)
Operational errors adalah error yang terjadi karena kondisi eksternal yang tidak ideal, tapi masih bisa diatasi. Contohnya: database connection timeout, API third-party down, atau disk space penuh.
Untuk operational errors, kita perlu strategi retry dan fallback. Jangan langsung menyerah saat pertama kali gagal.
// Contoh retry mechanism dengan exponential backoff
async function fetchDataWithRetry(url, maxRetries = 3) {
for (let i = 0; i setTimeout(resolve, delay));
console.log(`Retry attempt ${i + 1} after ${delay}ms`);
}
}
}Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
3. Programming Errors (Bugs)
Ini adalah error yang seharusnya tidak pernah terjadi kalau code kita benar. Contohnya: null pointer exception, undefined variable, atau type mismatch.
Programming errors harus di-log dengan detail lengkap: stack trace, user context, environment variables, dan request data. Informasi ini sangat penting untuk debugging.
// Contoh comprehensive error logging
function logError(error, context = {}) {
const errorLog = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
severity: 'error',
// User context
userId: context.userId,
userEmail: context.userEmail,
// Request context
url: context.url,
method: context.method,
headers: sanitizeHeaders(context.headers),
body: sanitizeBody(context.body),
// System context
environment: process.env.NODE_ENV,
nodeVersion: process.version,
platform: process.platform
};
// Send to logging service
logger.error(errorLog);
// Send to monitoring service (e.g., Sentry)
Sentry.captureException(error, { contexts: errorLog });
}Perhatikan bahwa kita melakukan sanitization terhadap headers dan body. Jangan sampai password atau token tersimpan di log.
Membangun Error Response yang Konsisten
Konsistensi dalam error response sangat membantu frontend developer dan API consumer. Mereka bisa memprediksi struktur error dan menanganinya dengan lebih baik.
Berikut adalah contoh struktur error response yang recommended:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Data yang Anda masukkan tidak valid",
"details": [
{
"field": "email",
"message": "Format email tidak valid"
},
{
"field": "password",
"message": "Password minimal 8 karakter"
}
],
"timestamp": "2026-07-04T10:30:00Z",
"requestId": "req_abc123xyz"
}
}Request ID sangat penting untuk tracing. Ketika user melaporkan error, mereka bisa memberikan request ID dan Anda bisa langsung mencari log yang relevan.
Error Codes yang Meaningful
Jangan hanya mengandalkan HTTP status code. Buat error code internal yang lebih spesifik.
HTTP status code hanya memberi tahu kategori error (4xx untuk client error, 5xx untuk server error), tapi tidak memberikan detail spesifik.
// Contoh error codes yang spesifik
const ErrorCodes = {
// Authentication errors (AUTH_xxx)
AUTH_INVALID_CREDENTIALS: 'AUTH_001',
AUTH_TOKEN_EXPIRED: 'AUTH_002',
AUTH_INSUFFICIENT_PERMISSIONS: 'AUTH_003',
// Validation errors (VAL_xxx)
VAL_REQUIRED_FIELD: 'VAL_001',
VAL_INVALID_FORMAT: 'VAL_002',
VAL_OUT_OF_RANGE: 'VAL_003',
// Business logic errors (BIZ_xxx)
BIZ_INSUFFICIENT_BALANCE: 'BIZ_001',
BIZ_PRODUCT_OUT_OF_STOCK: 'BIZ_002',
BIZ_DUPLICATE_TRANSACTION: 'BIZ_003',
// System errors (SYS_xxx)
SYS_DATABASE_ERROR: 'SYS_001',
SYS_EXTERNAL_SERVICE_ERROR: 'SYS_002',
SYS_UNKNOWN_ERROR: 'SYS_999'
};Dengan error code yang jelas, frontend developer bisa memberikan handling khusus untuk setiap jenis error.
Global Error Handler untuk Menangkap Semua Error
Tidak peduli seberapa hati-hati kita menulis code, pasti ada error yang lolos. Untuk itu, kita perlu global error handler sebagai safety net terakhir.
Global Error Handler di Express.js
// Global error handler middleware
app.use((error, req, res, next) => {
// Log error dengan context lengkap
logError(error, {
userId: req.user?.id,
url: req.originalUrl,
method: req.method,
headers: req.headers,
body: req.body
});
// Tentukan status code berdasarkan error type
let statusCode = 500;
let errorCode = ErrorCodes.SYS_UNKNOWN_ERROR;
let message = 'Terjadi kesalahan pada server';
if (error.name === 'ValidationError') {
statusCode = 422;
errorCode = ErrorCodes.VAL_INVALID_FORMAT;
message = 'Data tidak valid';
} else if (error.name === 'UnauthorizedError') {
statusCode = 401;
errorCode = ErrorCodes.AUTH_INVALID_CREDENTIALS;
message = 'Anda tidak memiliki akses';
}
// Jangan expose internal error di production
if (process.env.NODE_ENV === 'production') {
delete error.stack;
}
res.status(statusCode).json({
success: false,
error: {
code: errorCode,
message: message,
requestId: req.id,
timestamp: new Date().toISOString()
}
});
});Global Error Handler di Laravel
Laravel sudah menyediakan exception handler yang powerful. Anda hanya perlu meng-customize di app/Exceptions/Handler.php.
public function render($request, Throwable $exception)
{
// Custom handling untuk berbagai exception
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'success' => false,
'error' => [
'code' => 'RESOURCE_NOT_FOUND',
'message' => 'Data yang Anda cari tidak ditemukan'
]
], 404);
}
if ($exception instanceof AuthenticationException) {
return response()->json([
'success' => false,
'error' => [
'code' => 'UNAUTHENTICATED',
'message' => 'Silakan login terlebih dahulu'
]
], 401);
}
// Log error ke monitoring service
if ($this->shouldReport($exception)) {
Log::error($exception->getMessage(), [
'exception' => get_class($exception),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);
}
return parent::render($request, $exception);
}Error Monitoring dan Alerting
Logging saja tidak cukup. Anda perlu sistem monitoring yang bisa mendeteksi anomali dan mengirim alert saat terjadi masalah serius.
Integrasi dengan Sentry
Sentry adalah salah satu error monitoring tool yang paling populer. Setup-nya sangat mudah dan gratis untuk proyek kecil.
// Setup Sentry di Node.js
const Sentry = require('@sentry/node');
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
// Set sample rate untuk production
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Capture breadcrumbs
beforeSend(event, hint) {
// Sanitize sensitive data
if (event.request) {
delete event.request.cookies;
delete event.request.headers?.authorization;
}
return event;
}
});
// Capture exception dengan context
Sentry.captureException(error, {
tags: {
feature: 'payment',
severity: 'high'
},
user: {
id: user.id,
email: user.email
},
extra: {
orderId: order.id,
amount: order.total
}
});Setup Alerting yang Smart
Jangan set alert untuk setiap error. Anda akan kebanjiran notifikasi dan akhirnya mengabaikan semuanya.
Alert hanya untuk error yang benar-benar critical:
- Error rate meningkat drastis (misalnya lebih dari 10 error per menit)
- Critical endpoint down (payment, authentication, checkout)
- Database connection pool exhausted
- Memory atau CPU usage mencapai 90%
- Error yang mempengaruhi banyak user (lebih dari 10 user dalam 5 menit)
Circuit Breaker Pattern untuk External Services
Ketika aplikasi Anda bergantung pada external service (payment gateway, SMS provider, email service), Anda perlu circuit breaker pattern.
Circuit breaker mencegah aplikasi Anda terus-menerus mencoba hit service yang sedang down, yang hanya akan memperburuk situasi.
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() = this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
// Alert ops team
alertOps(`Circuit breaker OPEN for ${this.serviceName}`);
}
}
}
// Usage
const paymentGatewayBreaker = new CircuitBreaker(5, 60000);
async function processPayment(data) {
try {
return await paymentGatewayBreaker.call(async () => {
return await paymentGateway.charge(data);
});
} catch (error) {
// Fallback: queue payment for later processing
await queuePayment(data);
return { status: 'queued', message: 'Payment akan diproses segera' };
}
}Graceful Degradation untuk User Experience
Ketika terjadi error, jangan buat seluruh aplikasi mati. Implementasikan graceful degradation agar user masih bisa menggunakan fitur-fitur lain.
Contoh: jika fitur recommendation produk error, tampilkan produk terpopuler sebagai fallback. User tetap bisa berbelanja meskipun recommendation engine-nya bermasalah.
async function getProductRecommendations(userId) {
try {
const recommendations = await mlService.getRecommendations(userId);
return recommendations;
} catch (error) {
// Log error tapi jangan crash
logError(error, { feature: 'recommendations', userId });
// Fallback ke produk terpopuler
try {
return await Product.getTopSelling(10);
} catch (fallbackError) {
// Last resort: return empty array
logError(fallbackError, { feature: 'fallback_recommendations' });
return [];
}
}
}Testing Error Handling
Error handling yang tidak di-test sama saja dengan tidak ada. Anda harus memastikan bahwa error handling code Anda benar-benar berfungsi.
Unit Testing Error Scenarios
describe('Payment Service', () => {
it('should handle payment gateway timeout', async () => {
// Mock payment gateway to throw timeout error
paymentGateway.charge.mockRejectedValue(
new Error('ETIMEDOUT')
);
const result = await processPayment({
amount: 100000,
userId: 'user123'
});
// Should fallback to queue
expect(result.status).toBe('queued');
expect(queuePayment).toHaveBeenCalled();
});
it('should retry on temporary failure', async () => {
// Mock to fail twice then succeed
paymentGateway.charge
.mockRejectedValueOnce(new Error('Connection failed'))
.mockRejectedValueOnce(new Error('Connection failed'))
.mockResolvedValue({ success: true });
const result = await processPaymentWithRetry({
amount: 100000
});
expect(result.success).toBe(true);
expect(paymentGateway.charge).toHaveBeenCalledTimes(3);
});
});Integration Testing dengan Chaos Engineering
Untuk aplikasi production yang critical, pertimbangkan untuk melakukan chaos engineering: sengaja membuat error di environment staging untuk test resilience aplikasi Anda.
Beberapa skenario yang bisa ditest:
- Database connection tiba-tiba drop
- External API memberikan response yang lambat (5+ detik)
- Disk space mendekati penuh
- Memory leak yang gradual
- Network partition antara service
Best Practices Error Handling di Production
Berikut adalah checklist best practices yang harus Anda terapkan:
1. Jangan Expose Informasi Sensitif
Never return stack trace atau internal error message ke user di production. Ini adalah celah security yang serius.
2. Gunakan Correlation ID
Setiap request harus punya unique ID yang bisa digunakan untuk tracing dari frontend sampai database.
3. Set Up Health Check Endpoint
Buat endpoint khusus untuk health check yang bisa monitor semua dependencies: database, cache, external services.
app.get('/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
checks: {}
};
// Check database
try {
await db.query('SELECT 1');
health.checks.database = 'healthy';
} catch (error) {
health.checks.database = 'unhealthy';
health.status = 'degraded';
}
// Check Redis
try {
await redis.ping();
health.checks.cache = 'healthy';
} catch (error) {
health.checks.cache = 'unhealthy';
health.status = 'degraded';
}
const statusCode = health.status === 'healthy' ? 200 : 503;
res.status(statusCode).json(health);
});4. Implement Rate Limiting untuk Error Logging
Jika satu error terjadi ribuan kali per detik, jangan log semuanya. Ini akan membuat logging system Anda overload.
Gunakan sampling: log error pertama, lalu sample setiap 10% atau 1% error berikutnya.
5. Documentation untuk Error Codes
Dokumentasikan semua error code yang mungkin terjadi beserta cara mengatasinya. Ini sangat membantu frontend developer dan support team.
Kesimpulan
Error handling yang robust adalah investasi jangka panjang. Di awal mungkin terasa ribet dan memakan waktu, tapi benefit-nya sangat besar ketika aplikasi sudah masuk production.
User akan mendapatkan error message yang jelas dan helpful. Developer akan bisa debugging dengan cepat karena punya informasi yang lengkap. Dan ops team akan bisa detect dan resolve issue sebelum user complain.
Mulai dari hal kecil: pastikan setiap error di-log dengan baik, buat error response yang konsisten, dan setup monitoring service seperti Sentry.
Seiring waktu, tambahkan retry mechanism, circuit breaker, dan graceful degradation untuk membuat aplikasi Anda semakin resilient.
Ingat: aplikasi yang baik bukan aplikasi yang tidak pernah error, tapi aplikasi yang bisa handle error dengan graceful dan recover dengan cepat.