Course contentsShow
← Node.js
Lesson 1229 of 1,98011. Task Queues and Background ProcessingFree lesson

Implementing Transactional Outbox Pattern

Avoid data inconsistency by writing to a local database and a message broker within the same atomic scope.

In this lesson, you will learn how to guarantee data consistency between your database and message broker by using the Transactional Outbox pattern to prevent "dual-write" failures.

The Dual-Write Problem

When your Node.js application needs to update a database (e.g., creating a user) and notify a message broker (e.g., sending a welcome email event), you face a risk: what happens if the database commit succeeds, but the message broker goes offline before you can publish the event? You now have a database record with no corresponding downstream action, leading to inconsistent system state.

How the Outbox Pattern Works

Instead of sending the message directly to the broker, you insert the message into an outbox table within the same database transaction as your main business logic. Because both operations occur in the same atomic database scope, they either both succeed or both fail.

A separate "Relay" background process then polls the outbox table, reads the pending messages, publishes them to the broker, and marks them as processed upon confirmation.

// Example: Atomic write within a transaction
async function createUser(userData) {
  const connection = await pool.getConnection();
  await connection.beginTransaction();

  try {
    // 1. Update your business table
    await connection.query('INSERT INTO users ...', [userData]);

    // 2. Save the message to the local outbox table (same transaction)
    await connection.query(
      'INSERT INTO outbox (payload, status) VALUES (?, ?)',
      [JSON.stringify({ event: 'user_created', data: userData }), 'PENDING']
    );

    await connection.commit();
  } catch (err) {
    await connection.rollback();
    throw err;
  }
}

Why this is safer

By moving the publication step out of the request-response cycle, you decouple your application from the availability of your message broker. If the broker is momentarily unreachable, your background relay simply retries until the message is acknowledged, ensuring that the message is "at-least-once" delivered. This pattern creates a reliable bridge between your persistence layer and your event-driven architecture, effectively eliminating the risk of lost events during network partitions.

Key Takeaway: By storing outbound messages in your local database alongside business data within a single transaction, you ensure that no event is lost, even if your message broker is temporarily unavailable.