Fullstack CourseLearn by building
Back to week 4

Topic

Background work with RabbitMQ

Definition

A message queue such as RabbitMQ decouples a producer that publishes work from one or more consumers that process it asynchronously, so the original HTTP request can return before the work completes.

In simpler words

RabbitMQ lets Nest say do this later instead of making a user wait for slow or unreliable work inside the request.

The same conceptual lens as Redis and file storage: recognize when work does not belong inside the request and response cycle, without installing anything today.

After this you can

  • Identify work that belongs in a queue instead of an HTTP handler
  • Explain producer, consumer, queue, and exchange at a conceptual level
  • Describe retry and dead-letter behavior for failed jobs
  • Reason about at-least-once delivery and idempotency

What belongs off the request thread

Definition

Background work is any task whose completion the caller does not need to wait for synchronously, such as sending email, generating a report, or calling a slow third-party API.

In simpler words

If the user does not need the result immediately, do not make them wait for it.

Good candidates: sending a ticket-assigned email, generating a PDF export, calling a flaky third-party webhook, or resizing an uploaded image.

Bad candidates: anything the immediate HTTP response depends on, or work so fast that a queue only adds latency and infrastructure risk.

The controller publishes a small message, such as ticket 123 was created, and returns 201 immediately. A separate consumer process performs the slow part.

Producer stays fast

@Post()
async create(@Body() dto: CreateTicketDto) {
  const ticket = await this.ticketsService.create(dto);
  await this.queue.publish('ticket.created', { ticketId: ticket.id });
  return ticket; // 201 returns immediately, email happens later
}

The controller never awaits the email send; it only awaits publishing a tiny message.

Delivery guarantees and idempotency

Definition

At-least-once delivery means a message queue may redeliver a message after a failure, so a consumer must be written to tolerate processing the same message more than once without causing duplicate side effects.

In simpler words

A queue may hand a consumer the same job twice. The consumer must not send two emails or trigger two side effects because of that.

Most brokers, including RabbitMQ with manual acknowledgment, guarantee at-least-once, not exactly-once. Consumers must be idempotent by checking whether the work was already done before doing it again.

A dead-letter queue captures messages that fail repeatedly, so a bad job does not loop forever and drown out healthy work.

Retries need backoff and a limit. Infinite immediate retries against a broken consumer will hammer the same downstream failure endlessly.

RabbitMQ versus a raw timer or cron hack

Definition

A message broker persists and routes jobs independently of any single process, whereas an in-process timer or cron job loses its state and schedule whenever that process restarts or scales to multiple instances.

In simpler words

A queue survives a restart and works correctly with several Nest instances. An in-memory timer does not.

A timer-based background job disappears on deploy or restart, and runs once per Nest instance if you scale horizontally, producing duplicate work with no coordination.

RabbitMQ, or a similar broker, durably stores a job until a consumer acknowledges it, and only one consumer among many instances receives each message.

Reach for a queue once work must survive restarts, run once regardless of instance count, or needs retry and backoff semantics, not merely because asynchronous sounds appealing.

Ack, nack, and poison messages

Definition

Acknowledgement (ack) tells the broker the consumer finished the message successfully; negative acknowledgement (nack/reject) tells the broker to requeue or dead-letter it; a poison message is one that repeatedly fails and must not block the queue forever.

In simpler words

Until you ack, RabbitMQ may redeliver the message — which is why idempotency matters.

Ack only after the side effect succeeded (or after you recorded that it is done). Acking before the work finishes can drop work on a crash.

Nack with requeue for transient failures (network blip). After N failures, route to a dead-letter queue for humans to inspect.

Poison messages that crash consumers on every attempt will stall workers if you keep requeueing infinitely — dead-letter is the escape hatch.

Message payloads should be small ids + event type (ticket.created, ticketId), not entire entities that go stale.

Idempotent consumer sketch

async handleTicketCreated(msg: { ticketId: string; eventId: string }) {
  if (await this.outbox.alreadyProcessed(msg.eventId)) return; // ack safely
  await this.mailer.sendAssignedEmail(msg.ticketId);
  await this.outbox.markProcessed(msg.eventId);
}

A unique eventId (or natural key) lets redelivery become a no-op instead of a duplicate email.

Keep in mind

  • Keep the producer fast and let the consumer be slow; never make the HTTP response wait on the queue.
  • Design every consumer assuming it might see the same message twice.
  • A dead-letter queue is not optional polish; it is how broken jobs get noticed.
  • Ack after success; dead-letter after repeated failure; keep payloads small.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines background work with RabbitMQ?
Syntax2. Which code-level choice matches background work with RabbitMQ?
Practical3. A reviewer spots a bug related to background work with RabbitMQ. What is the right fix?
Logic4. Which reasoning correctly explains background work with RabbitMQ?
Concept5. Which boundary does correct use of background work with RabbitMQ preserve?
Practical6. What is the safest next step when applying background work with RabbitMQ?
Syntax7. Which implementation direction matches the rule for background work with RabbitMQ?
Logic8. Which consequence follows from applying background work with RabbitMQ correctly?
Concept9. Which claim about background work with RabbitMQ is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates background work with RabbitMQ?