Membangun Arsitektur Event-Driven Berkinerja Tinggi Menggunakan NestJS, BullMQ, dan Redis

Membebaskan web server dari proses komputasi sinkron yang lambat: cara mengorkestrasi pengiriman email massal, sinkronisasi invoice ERP, dan pemrosesan webhook melalui antrean background jobs yang resilient dan scalable.

PuluhLabs Engineering Team, Senior Backend Engineer · 9 November 2026 · 6 min baca

Membangun Arsitektur Event-Driven Berkinerja Tinggi Menggunakan NestJS, BullMQ, dan Redis
Pengembangan Web
DECOUPLED

Isi artikel ini

  • Mengapa Memilih BullMQ di Atas Redis?
  • Mengonfigurasi Modul BullMQ pada NestJS
  • Memproduksi Job di Controller (Fast HTTP Response)
  • Mengonsumsi Job di Worker Processor dengan Idempotency
  • Praktik Terbaik Manajemen Antrean di Lingkungan Produksi

Dalam pengembangan aplikasi web B2B dan portal enterprise, banyak operasi bisnis yang membutuhkan waktu eksekusi relatif lama: menerbitkan laporan keuangan berformat PDF, menyinkronkan ribuan inventaris stok ke ERP pihak ketiga, mengirimkan notifikasi multi-saluran (Email, SMS, WhatsApp), hingga memproses kalkulasi analitik kompleks.

Jika operasi-operasi ini dieksekusi secara sinkron di dalam siklus request-response HTTP utama, dua dampak buruk akan langsung dirasakan:

  1. Pengguna mengalami waktu tunggu yang sangat lama (latensi HTTP membengkak dari 50 milidetik menjadi belasan detik).
  2. Server web berisiko mengalami thread exhaustion saat terjadi lonjakan trafik bersamaan, menyebabkan request sederhana lainnya ikut tertahan dan berakhir dengan status HTTP 504 Gateway Timeout.

Solusi arsitektur yang paling teruji untuk masalah ini adalah memisahkan penanganan request dari pemrosesan logika bisnis menggunakan Arsitektur Event-Driven berbasis antrean pesan (message queue) terdistribusi dengan NestJS, BullMQ, dan Redis.

Mengapa Memilih BullMQ di Atas Redis? #

Banyak pengembang pemula mencoba membuat antrean sendiri menggunakan struktur data Redis List (LPUSH dan RPOP). Meskipun sederhana, pendekatan manual tersebut rentan terhadap kehilangan pesan (message loss) apabila proses worker mengalami crash di tengah eksekusi task.

BullMQ adalah pustaka antrean pesan NodeJS canggih berbasis Redis Streams dan skrip Lua atomik yang menyediakan fitur keandalan tingkat enterprise:

  • Jaminan Pengiriman Minimal Sekali (At-Least-Once Delivery): Pesan tidak dihapus dari Redis sampai proses worker secara eksplisit mengonfirmasi bahwa tugas telah selesai dieksekusi.
  • Percobaan Ulang Otomatis dengan Backoff Eksponensial: Menghindari kegagalan sementara (transient errors) pada API pihak ketiga dengan menunda pengiriman ulang secara proporsional.
  • Dead-Letter Queue (DLQ): Mengisolasi tugas yang gagal berulang kali ke antrean khusus untuk diinvestigasi oleh engineer tanpa menyumbat antrean utama.
  • Manajemen Konkurensi Terkontrol: Membatasi jumlah job paralel per worker untuk mencegah kehabisan memori CPU pada server database.
text
Arsitektur Antrean Event-Driven NestJS & BullMQ:
Browser Klien ────> [NestJS HTTP Controller]
                          │ (Menaruh Job ke Redis dalam 2 milidetik)
                          ▼ Balas Segera: HTTP 202 Accepted { jobId: '123' }
                  ┌──────────────────────────────────────────────┐
                  │ Redis (BullMQ Queue Cluster)                 │
                  │ - Antrean Aktif (Waiting / Active)           │
                  │ - Antrean Tertunda (Delayed / Retry)         │
                  │ - Antrean Gagal (Dead-Letter Queue)          │
                  └──────────────────────────────────────────────┘
                          │ (Worker menarik tugas secara asynchronous)
                          ▼
                  [NestJS Worker / Processor]
                  - Sinkronisasi Data ERP SAP
                  - Rendering Dokumen Faktur Pajak
                  - Mengirim Notifikasi WhatsApp Gateway

Mengonfigurasi Modul BullMQ pada NestJS #

Pasang pustaka yang diperlukan:

bash
npm install @nestjs/bullmq bullmq ioredis

Definisikan konfigurasi koneksi Redis terpusat di dalam app.module.ts:

typescript
// backend/src/app.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';

@Module({
  imports: [
    BullModule.forRoot({
      connection: {
        host: process.env.REDIS_HOST || '127.0.0.1',
        port: parseInt(process.env.REDIS_PORT || '6379', 10),
        password: process.env.REDIS_PASSWORD || undefined,
        maxRetriesPerRequest: null, // Wajib disetel null untuk BullMQ
        enableReadyCheck: false,
      },
    }),
    // Registrasi antrean spesifik domain invoice
    BullModule.registerQueue({
      name: 'invoice-processing',
    }),
  ],
})
export class AppModule {}

Memproduksi Job di Controller (Fast HTTP Response) #

Controller menerima payload transaksi dari klien, memvalidasi format DTO, menyisipkan task ke antrean BullMQ, dan langsung mengembalikan respons HTTP 202 Accepted kepada pengguna dalam waktu kurang dari 5 milidetik:

typescript
// backend/src/modules/invoice/invoice.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';

@Controller('invoices')
export class InvoiceController {
  constructor(
    @InjectQueue('invoice-processing') private readonly invoiceQueue: Queue
  ) {}

  @Post('generate')
  @HttpCode(HttpStatus.ACCEPTED)
  async createInvoice(@Body() dto: { orderId: string; clientEmail: string }) {
    // Sisipkan job ke antrean dengan konfigurasi ketahanan (resilience)
    const job = await this.invoiceQueue.add(
      'process-tax-invoice',
      {
        orderId: dto.orderId,
        clientEmail: dto.clientEmail,
        queuedAt: new Date().toISOString(),
      },
      {
        attempts: 5, // Percobaan ulang maksimal 5 kali jika gagal
        backoff: {
          type: 'exponential',
          delay: 5000, // Mulai dari jeda 5 detik, 10s, 20s, dst
        },
        removeOnComplete: 1000, // Simpan histori 1.000 job sukses terakhir
        removeOnFail: 5000,     // Simpan histori job gagal untuk audit
        jobId: `invoice-${dto.orderId}`, // Mencegah duplikasi job untuk order yang sama
      }
    );

    return {
      status: 'accepted',
      message: 'Faktur sedang diproses di latar belakang',
      jobId: job.id,
    };
  }
}

Mengonsumsi Job di Worker Processor dengan Idempotency #

Worker bertanggung jawab menjalankan komputasi berat. Sangat penting menerapkan prinsip Idempotency agar jika sebuah job dijalankan ulang akibat network glitch sesaat, sistem tidak menghasilkan dokumen ganda atau tagihan ganda ke pelanggan:

typescript
// backend/src/modules/invoice/invoice.processor.ts
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
import { Logger } from '@nestjs/common';

@Processor('invoice-processing', { concurrency: 5 }) // Menjalankan 5 job bersamaan
export class InvoiceProcessor extends WorkerHost {
  private readonly logger = new Logger(InvoiceProcessor.name);

  async process(job: Job<any, any, string>): Promise<any> {
    this.logger.log(`Memulai pemrosesan Job ID: ${job.id} untuk Order: ${job.data.orderId}`);

    switch (job.name) {
      case 'process-tax-invoice': {
        const { orderId, clientEmail } = job.data;

        // 1. Pemeriksaan Idempotensi: Cek apakah invoice sudah terbit di database
        const isAlreadyProcessed = await this.checkExistingInvoice(orderId);
        if (isAlreadyProcessed) {
          this.logger.warn(`Invoice untuk Order ${orderId} sudah pernah diproses. Melewati langkah.`);
          return { status: 'skipped', reason: 'already_exists' };
        }

        // 2. Eksekusi kalkulasi pajak dan integrasi API ERP
        const pdfBuffer = await this.generatePdfDocument(orderId);
        await this.syncToErpSystem(orderId, pdfBuffer);

        // 3. Kirim notifikasi email ke klien
        await this.sendEmailNotification(clientEmail, pdfBuffer);

        this.logger.log(`Selesai memproses Job ID: ${job.id}`);
        return { status: 'success', orderId };
      }

      default:
        throw new Error(`Tipe job tidak dikenali: ${job.name}`);
    }
  }

  private async checkExistingInvoice(orderId: string): Promise<boolean> {
    // Logika pengecekan status invoice pada tabel database
    return false;
  }

  private async generatePdfDocument(orderId: string): Promise<Buffer> {
    // Logika komputasi pembuatan dokumen PDF
    return Buffer.from('PDF_CONTENT');
  }

  private async syncToErpSystem(orderId: string, pdf: Buffer): Promise<void> {
    // Panggilan API eksternal menuju ERP SAP / Odoo
  }

  private async sendEmailNotification(email: string, pdf: Buffer): Promise<void> {
    // Pengiriman email via SMTP stackmail
  }
}

Praktik Terbaik Manajemen Antrean di Lingkungan Produksi #

  1. Pemisahan Proses Web dan Worker: Di lingkungan produksi, jalankan worker BullMQ sebagai proses PM2 atau container Kubernetes terpisah dari instance API gateway. Jika worker mengalami lonjakan pemakaian CPU saat merender dokumen berat, server API publik tetap responsif melayani pengunjung.
  2. Monitoring Kesehatan Antrean Menggunakan Bull-Board: Integrasikan dashboard antarmuka visual @bull-board/nestjs di rute admin privat untuk memantau status antrean (Waiting, Active, Completed, Failed) secara real-time.
  3. Penyetelan Konkurensi Sesuai Batas Database: Jangan menyetel parameter concurrency terlalu tinggi tanpa memeriksa kapasitas connection pool database PostgreSQL. Batas konkurensi 5 hingga 10 per core prosesor merupakan acuan yang aman.
  4. Alarm Otomatis untuk Dead-Letter Queue: Pasang listener event kegagalan (@OnWorkerEvent('failed')) yang secara otomatis mengirimkan notifikasi peringatan ke saluran Discord atau grup WhatsApp engineering jika suatu tugas gagal setelah batas maksimum percobaan ulang.
Bagikan Artikel Ini:
Topik Terkait:RedisArsitektur SoftwareERP IntegrationPerformanceWebhook Idempotency
Istilah Teknis Terkait (Knowledge Graph Glossary)
Jejaring Entitas & Solusi Teknis Terkait
3 Solusi Terintegrasi
Architecture Matrix

Perbandingan Headless CMS vs Monolitik

Kajian komparatif performa, keamanan OWASP, dan total biaya kepemilikan (TCO).

Buka Solusi
Planning Tool

Kalkulator Investasi & Jadwal Website

Estimasi biaya pembuatan website company profile, portal B2B, atau web app kustom.

Buka Solusi
Partnership Hub

Kemitraan White-Label untuk Agensi

Dukungan rekayasa teknis rahasia di balik layar agensi Anda dengan NDA resmi.

Buka Solusi
TO
PuluhLabs Engineering TeamPenulis Terverifikasi

Senior Backend Engineer

Praktisi dan konsultan pengembangan platform digital terpercaya dengan pengalaman memimpin perancangan lebih dari 50+ proyek untuk skala korporat, bisnis B2B, dan instansi profesional. Berfokus pada pembangunan solusi web yang tangguh, cepat, aman, serta dirancang untuk mendorong pertumbuhan bisnis jangka panjang.

Ditinjau & divalidasi oleh: Tim Profesional PuluhLabs
Terakhir ditinjau: 9 November 2026

Ingin Menerapkan Standar Performa Ini pada Proyek Agensi Anda?

Tim PuluhLabs siap menjadi mitra rekayasa digital rahasia (white-label) untuk agensi Anda. Garansi performa skor Lighthouse 90+, Core Web Vitals hijau, dan 100% hak milik kode.

Konsultasi Proyek Bersama Tim PuluhLabs

Artikel Rekomendasi Selanjutnya

Lihat Semua
  1. Pengembangan Web

    Arsitektur Micro-Frontends vs Modular Monolith: Panduan Memilih untuk Aplikasi Web Skala Enterprise

  2. Pengembangan Web

    Implementasi Zero-Trust Architecture pada REST API dan Webhook Korporat

  3. Pengembangan Web

    Optimasi Query PostgreSQL Skala Jutaan Baris: Indexing, Table Partitioning, dan Connection Pooling