Belajar Fullstack Developer - Background Jobs & Queues
Episode 13 of 28

Belajar Fullstack Developer - Background Jobs & Queues

Memindahkan kerja berat ke background: memahami konsep queue, broker, dan worker, membangun background job dengan BullMQ dan Redis, menjadwalkan tugas berulang dengan cron, serta pola retry dan idempotency.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setiap aplikasi akhirnya punya kerja yang terlalu lambat untuk dijalankan di dalam request: kirim email, proses gambar, sinkronisasi dengan API eksternal. Jika kalian menjalankannya secara sinkron, pengguna menunggu berdetik-detik — dan jika gagal di tengah, semua harus mulai ulang. Episode ini membahas background jobs & queues: memindahkan kerja berat keluar dari request pengguna.

Mengapa topik ini wajib untuk fullstack? Karena "fitur selesai" di dunia nyata hampir selalu berarti kerja latar: setelah user submit, ada serangkaian proses yang harus berjalan di belakang tanpa membuat user menunggu.

Konsep: Producer, Broker, Worker

Bayangkan sebuah dapur restoran: pelanggan memesan (producer), pesanan diletakkan di papan jendela (broker/queue), dan koki mengambil pesanan satu per satu (worker). Ketika pelanggan selesai memesan, ia tidak menunggu di depan koki — ia kembali ke meja.

100%
PeranTugasContoh
ProducerMengirim job ke queueServer action setelah submit
BrokerMenyimpan & mengantre jobRedis + BullMQ
WorkerMemproses job dari queueProses terpisah, retry otomatis

Keuntungan strukturalnya: server action selesai cepat (cukup menaruh job ke queue), dan pekerjaan diproses async dengan retry jika gagal.

Setup BullMQ + Redis

Kita pakai BullMQ di atas Redis — pasangan paling umum untuk Node.js. Jalankan Redis via Docker (seperti PostgreSQL di episode 0):

Jalankan Redis via Docker
docker run -d --name redis-fullstack -p 6379:6379 redis:7-alpine
Install BullMQ
pnpm add bullmq ioredis
src/lib/queue.ts
import { Queue, Worker } from "bullmq"
 
const connection = { host: "localhost", port: 6379 }
 
export const noteQueue = new Queue("notes", { connection })

Praktik 1: Email Setelah Membuat Catatan

Dulu (sinkron): user menunggu SMTP yang lambat. Sekarang: producer menaruh job, worker yang mengirim.

Producer — dipanggil dari server action (tidak membuat user menunggu):

Mengantre job dari server action
"use server"
 
import { noteQueue } from "@/lib/queue"
 
export async function createNote(formData: FormData) {
  const note = await prisma.note.create({ data: {...} })
  await noteQueue.add("send-email", {
    noteId: note.id,
    email: user.email,
  })
  revalidatePath("/notes")
}

Worker — proses terpisah yang menjalankan job:

src/worker/email.ts
import { Worker } from "bullmq"
import { sendEmail } from "@/lib/email"
 
const worker = new Worker(
  "notes",
  async (job) => {
    if (job.name === "send-email") {
      await sendEmail({
        to: job.data.email,
        subject: "Catatan baru dibuat",
        body: `Catatan ${job.data.noteId} berhasil disimpan`,
      })
    }
  },
  { connection },
)

Jalankan worker dengan perintah terpisah:

Jalankan worker
pnpm tsx src/worker/email.ts

Tip

Producer dan worker adalah proses terpisah — itulah intinya. Server action hanya "menaruh pesanan", worker "memasak" di prosesnya sendiri. Ini berarti aplikasi kalian bisa memproses banyak job bersamaan tanpa memperlambat response pengguna.

Retry dan Idempotency

Kerja latar gagal — jaringan putus, API eksternal down. Queue dirancang untuk ini: BullMQ retry otomatis dengan backoff:

Job dengan retry & backoff
await noteQueue.add("send-email", { noteId }, {
  attempts: 5,           // coba 5 kali
  backoff: { type: "exponential", delay: 2000 },
})

Aturan penting untuk worker: idempotent — menjalankan job yang sama dua kali harus menghasilkan efek yang sama. Contoh: email yang gagal di retry tidak boleh terkirim ganda:

Cek idempotency sebelum aksi
const sent = await prisma.emailLog.findUnique({ where: { noteId } })
if (sent) return  // sudah terkirim, lewati
await sendEmail({ to, subject, body })
await prisma.emailLog.create({ data: { noteId } })

Cron: Tugas Terjadwal

Untuk tugas rutin (ringkasan harian, hapus data basi, backup), pakai cron. BullMQ menyediakan repeatable job:

Job terjadwal setiap pukul 06.00
await noteQueue.add("daily-summary", {}, {
  repeat: { pattern: "0 6 * * *" },
})

Sintaks cron "0 6 * * *" berarti "menit 0, jam 6, setiap hari". Jika butuh scheduler berbasis HTTP di Vercel/edge, episode 18 membahas alternatifnya (cron.json / edge cron).

Praktik: Background Job

Selesaikan dengan urutan berikut:

  1. Jalankan Redis dan install BullMQ.
  2. Buat queue notes dengan satu job send-email (bisa pakai logger sebagai pengganti email asli).
  3. Panggil queue.add dari server action createNote.
  4. Jalankan worker dan verifikasi: submit catatan → response langsung → beberapa detik kemudian log worker muncul.
Verifikasi job masuk
curl -s http://localhost:3000/notes/new -X POST ...
# di terminal worker:
# {"msg":"sending email for note ..."}

Jika log worker muncul tanpa membuat halaman menunggu, arsitektur queue kalian bekerja.

Warning

Bahaya tersembunyi queue: job menumpuk tanpa dipantau. Selalu pasang alert saat panjang queue melonjak, dan batasi retry dengan attempts — job yang gagal total sebaiknya masuk "dead letter" untuk ditangani manual, bukan dicoba selamanya.

Penutup

Inti yang harus dibawa pulang:

  • Kerja lambat (email, gambar, integrasi) harus pindah ke background job.
  • Tiga peran: producer (server action), broker (Redis/BullMQ), worker (proses terpisah).
  • Server action selesai cepat; worker yang memproses dengan retry dan backoff.
  • Worker wajib idempotent — retry tidak boleh menggandakan efek.
  • Cron untuk tugas berulang; pantau panjang queue agar tidak menumpuk.

Di episode 14 selanjutnya kita membahas real-time features — WebSocket, SSE, dan live updates — untuk membangun fitur yang bereaksi tanpa refresh halaman. Sampai jumpa di episode 14!

Belajar Fullstack Developer - Background Jobs & Queues | Belajar Fullstack