Memuat...
👋 Selamat Pagi!

Cara Membangun Error Handling Strategy yang Robust untuk Aplikasi Production

Error handling yang buruk bikin user kabur dan developer frustasi. Pelajari strategi lengkap menangani error di production seperti developer senior.

Cara Membangun Error Handling Strategy yang Robust untuk Aplikasi Production

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.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang