πŸš€ Node.js Comprehensive Guide

πŸ”„ Why Node.js?

πŸ”„ When to Choose Node.js:

πŸ”„ How Node.js Handles 10,000 Concurrent Requests Efficiently?

βš™οΈ LIBUV

🎯 Reactor Pattern

πŸ’» Code Example


const net = require('net');

const server = net.createServer((socket) => {
    console.log('Client connected');

    socket.on('data', (data) => {
        console.log('Received:', data.toString());
        socket.write('Echo: ' + data);
    });

    socket.on('end', () => {
        console.log('Client disconnected');
    });
});

server.listen(3000, () => {
    console.log('Server listening on port 3000');
});
    

πŸ”„ How to Implement Security in Node.js?

πŸ”„ Node.js Features

πŸ”„ How to Increase Node.js Performance

πŸ”„ How to track memory leaks?

Memory leaks occur when an application allocates memory but fails to release it when it's no longer needed, leading to a gradual increase in memory usage and potentially crashing the application.

Common Causes:

Tools for Detection

How to avoid Leakage:

πŸ”„ How to handle Back Pressure of stream in event loop?

Back pressure occurs when the producer (e.g., a readable stream) sends data faster than the consumer (e.g., a writable stream)

❌ Problem:

A Readable Stream (fast producer)
A Writable Stream (slow consumer)

The producer is pushing data every 10ms, but the consumer is only able to consume every 100ms. This will:

Eventually crash your app if the stream buffer overflows.

βœ… How to handle?

1. In Node JS stream, if you use pipe() method, it will automatically automatically manages back pressure β€” it pauses the readable stream when the writable stream is not ready to receive data.

2. If you're not using pipe(), you should manually do pause when the consumer is full and resume when it's ready again. write() method will return true / false. On drain event - resume when the writable stream is drained.

readableStream.on('data', (chunk) => {
const canContinue = writableStream.write(chunk);
if (!canContinue) {
readableStream.pause(); // pause the readable stream
}
});

writableStream.on('drain', () => {
readableStream.resume(); // resume when the writable stream is drained
});

πŸ”„ Explain Event Loop Phases:

1. Timers Phase

Executes callbacks scheduled by setTimeout() and setInterval().
Timers are not guaranteed to run at the exact delayβ€”only after the delay has passed.

2. Pending Callbacks Phase

Executes certain I/O callbacks deferred to the next loop iteration.
Examples include errors like ECONNREFUSED for TCP sockets on some Unix systems.

3. Idle, Prepare Phase

Internal use only by Node.js and libuv.
Used to prepare the system for the poll phase.

4. Poll Phase

Retrieves new I/O events and executes their callbacks.

If there are:

  • Ready I/O callbacks, they are executed.
  • No I/O and setImmediate() scheduled, move to check phase.
  • No I/O or setImmediate(), it waits for callbacks or moves to timers if one is due.
5. Check Phase

Executes callbacks scheduled via setImmediate().
Always comes after poll.
Useful when you want to run something immediately after I/O.

6. Close Callbacks Phase

Executes cleanup callbacks for closed resources.
This includes sockets, streams, servers, or any event emitters that emit a 'close' event.

Example: socket.on('close', ...) or server.close().

🧰 Node.js util Package

The util module in Node.js is a built-in core module that provides utility functions helpful for working with asynchronous code, debugging, and formatting.

You don’t need to install it β€” just require it:

const util = require('util');

πŸ› οΈ Common use cases include:

πŸ“Œ Example: Using util.promisify with fs.readFile

const fs = require('fs');
	const util = require('util');
	const readFile = util.promisify(fs.readFile);

	readFile('./file.txt', 'utf8')
	  .then(data => console.log(data))
	  .catch(err => console.error(err));

πŸ“Œ Example: Using util.callbackify to convert an async function

const util = require('util');

	async function fetchData() {
		return 'Fetched Data';
	}

	const callbackStyle = util.callbackify(fetchData);

	callbackStyle((err, result) => {
		if (err) throw err;
		console.log(result); // Fetched Data
	});

πŸ“Œ Example: Creating a reusable utility function with util.format

// utils/logger.js
	const util = require('util');

	function logInfo(name, value) {
		const message = util.format('INFO: %s has a value of %d', name, value);
		console.log(message);
	}

	module.exports = { logInfo };

	// usage in another file
	const { logInfo } = require('./utils/logger');
	logInfo('Speed', 80);

πŸš€ Use the util package when:

πŸ”„ Microtasks (nextTick, Promises) Are Prioritized Between Phases

The event loop in Node.js runs in multiple phases to handle asynchronous operations. Within these phases, microtasks (such as `process.nextTick()` and Promises) have higher priority over macrotasks (like `setTimeout()` and `setImmediate()`). This means microtasks will be executed before timers and I/O callbacks, even if they were queued after them.

Code Example:

setTimeout(() => console.log('timeout'), 0);
	setImmediate(() => console.log('immediate'));
	Promise.resolve().then(() => console.log('promise'));
	process.nextTick(() => console.log('nextTick'));

Execution Order:

nextTick
	promise
	timeout
	immediate

Explanation:

βœ… Diagram Summary:


	 JS Code
		↓
	β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
	β”‚ Call Stack  β”‚ ← Synchronous code runs here
	β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
		↓
	Async functions offloaded to:
	β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
	β”‚  Node APIs (libuv) β”‚ ← I/O, Timers, etc.
	β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
		↓
	Callback queues:
	β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
	β”‚ Microtasks    β”‚ Timers        β”‚ I/O Callbacks β”‚
	β”‚ (Promises,    β”‚ (setTimeout)  β”‚ (fs, net)     β”‚
	β”‚ nextTick)     β”‚               β”‚               β”‚
	β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
		↓
	β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
	β”‚ Event Loop  β”‚ ← Picks & executes tasks
	β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
		

πŸ” Key Points:

πŸš€ How is Node.js Most Commonly Used?

Node.js is a powerful JavaScript runtime built on Chrome's V8 engine. It's widely used for building fast, scalable, and real-time applications β€” especially those that are I/O-intensive.

πŸ’Ό Common use cases for Node.js include:

πŸ’‘ Why Node.js?

πŸ”š In short: Node.js is best suited for building fast, scalable network applications β€” especially those that rely heavily on asynchronous I/O operations.

πŸ”„ What is I/O in Node.js?

In Node.js, I/O (Input/Output) refers to any operation that involves reading from or writing to external resources.

Common I/O operations in Node.js include:

πŸ’‘ Node.js is designed around non-blocking asynchronous I/O, which means it can perform I/O operations in the background and continue executing other code without waiting for the I/O to finish.

πŸš€ This is made possible by the libuv library and the event loop, allowing Node.js to be highly performant and scalable, especially for I/O-heavy applications.

πŸ“Œ Example: Reading a file asynchronously

const fs = require('fs');

	fs.readFile('example.txt', 'utf8', (err, data) => {
		if (err) throw err;
		console.log(data);
	});

	console.log('File read initiated...');

πŸ” The above code starts reading the file, moves on immediately, and logs the content once it's ready β€” this is non-blocking I/O in action.

🧼 How to Write Clean and Maintainable Node.js Code

Writing clean and maintainable code in Node.js helps teams scale, reduces bugs, and makes your codebase easier to understand and extend.

Here are some best practices to follow:

πŸ“ 1. Follow a Clean Project Structure

🧠 2. Use Meaningful Naming

πŸ“¦ 3. Modularize Your Code

πŸ›‘οΈ 4. Add Error Handling

βœ… 5. Write Unit & Integration Tests

πŸ“ 6. Follow Linting and Formatting Rules

πŸ“– 7. Document Your Code

🚦 8. Use Environment Variables

πŸ“Š 9. Monitor & Log Properly

πŸ§ͺ 10. Stick to Consistent Code Style

βœ… Bonus Tips:

🧠 Clean Node.js code = easier to debug, faster to extend, and loved by your future self (or your team)!

πŸ”— Inter-Service Communication in Microservices

βš–οΈ Load Balancing & πŸš€ Caching in Distributed Node.js Systems

πŸ›’ Scalable Microservices-based eCommerce Backend (Node.js)

πŸ§ͺ Testing with Mocha & Chai

πŸ“‘ Understanding HTTP Status Codes

πŸš€ How to Handle Performance Bottlenecks in Node.js

πŸ› οΈ How to Design Scalable RESTful APIs in Node.js

🌐 What is a REST API?

βš™οΈ What is CI/CD?

☁️ What Cloud Services Have You Used?

πŸš€ How Do You Deploy and Monitor Node.js Apps in the Cloud?

Deployment

Monitoring

βš™οΈ What CI/CD Tools Have You Used?

πŸ” What is SAML?

SAML (Security Assertion Markup Language) is a standard that allows users to log in once and access multiple websites or services without needing to re-enter credentials.

It works by using an identity provider (IdP) to authenticate the user and then sending a secure "SAML assertion" to the service provider (SP) to confirm the user's identity.

This enables Single Sign-On (SSO), improving security and user convenience.

The process involves the IdP verifying the user, creating a SAML token, and passing it to the SP for access.

🧡 Node.js Cluster Module

πŸ’‘ Advantages of Using Cluster Module


	const cluster = require('cluster');
	const http = require('http');
	const os = require('os');

	const numCPUs = os.cpus().length;

	if (cluster.isMaster) {
	  console.log(`Master process PID: ${process.pid}`);
	  
	  for (let i = 0; i < numCPUs; i++) {
		cluster.fork();
	  }

	  cluster.on('exit', (worker, code, signal) => {
		console.log(\`Worker \${worker.process.pid} died\`);
		console.log('Starting a new worker...');
		cluster.fork(); // Restart the worker
	  });

	} else {
	  http.createServer((req, res) => {
		res.writeHead(200);
		res.end(\`Handled by worker \${process.pid}\`);
	  }).listen(3000);

	  console.log(\`Worker process PID: \${process.pid} is running\`);
	}
    

πŸ”€ Child Process vs Worker Thread

Feature Child Process Worker Thread
Purpose Runs another Node.js process (separate memory) Runs JS code in a separate thread (shared memory)
Use Case Heavy CPU or I/O tasks, different scripts Heavy CPU tasks within the same app
Communication Via messages (uses IPC – Inter-Process Communication) Via messages (but faster – shares memory)
Performance Slower due to process creation overhead Faster because it's in the same process
Memory Usage Higher (each child has its own memory) Lower (shared memory with main thread)
Crash Isolation Crash won’t affect main app Crash may affect main app
Module child_process module worker_threads module
Can run different code? Yes (you can run any Node.js file) Yes, but usually used for functions/tasks
Example fork(), spawn(), exec() new Worker()
Good for Running scripts, external tools, separate jobs Parallel tasks like calculations or data parsing

πŸ“‘ Node.js net Module

The net library in Node.js is a code Node JS module used to create TCP servers and clients.

  • It allows your app to communicate over the network using TCP or IPC (inter-process communication).
  • Useful for building chat servers or backend systems that don’t rely on HTTP.
  • Use net.createServer() to create a TCP server that handles incoming connections.
  • Use net.Socket to act as a client connecting to other servers.
  • It is event-driven, responding to events like data, connect, and close.

🌐 API Gateway

An API gateway is a software intermediary that manages interactions between clients and applications.

Key Functions of an API Gateway:

Example Tools for API Gateway:

🚧 Challenges Faced in Our Last eCommerce Project & Solutions

πŸ” 1. Authentication & Security

Challenge: We need to verify the user data is secure or not and properly preventing unauthorized access

Reason behind, team faced some malicious activity related to token misuse.

Solution:

  • πŸ§ͺ Started by debugging the code and reviewing the authentication flow.
  • πŸ” Verified the entire process and We have implemented token versioning to invalidate old tokens after sensitive actions.
  • β›” Added rate limiting to prevent brute-force attacks.
  • πŸ›‘οΈ Ensured strong input validation across all endpoints to protect against injection attacks.

Implementation:

  • πŸ—‚οΈ Added a tokenVersion field to the user's database entry.
  • πŸ“¦ JWT includes this tokenVersion.
  • πŸ”„ On password update, tokenVersion increments, invalidating old tokens.

πŸ’³ 2. Order Processing & Payment Integration

Challenge: Faced issue in correct order handling even when a payment fails or is delayed.

Solution:

  • πŸ›’ We have separated the order creation and payment confirmation step, to avoid marking orders as complete before payment success.
  • πŸ“‘ Used Stripe webhooks to listen for real-time payment status updates.
  • πŸ” Added retry code using axios-retry to handle temporary failures.

🚧 What are difficult situations you faced ?

βœ… 1. Scaling a Node.js App That Couldn’t Handle Traffic

Challenge: We had a large Node.js monolithic app which is handling login, product details, and payments. As traffic increased, during peak times, the system began to slow down significantly.

Solution:

  • 🧱 We Split the monolithic app into microservices β€” separate services for users, products, and authentication.
  • 🐳 Used Docker to containerize each service for consistent deployments across environments.
  • ☸️ Implemented Kubernetes to handle service orchestration and automatic scaling based on traffic.
  • ⚑ Integrated Redis for caching frequently requested data like product details.
  • πŸ“¨ Leveraged RabbitMQ for background tasks like email and notifications to offload the main app thread.

Implementation:

  • πŸ”§ Set up Kubernetes clusters with horizontal pod auto-scaling.
  • πŸ—ƒοΈ Cached hot data in Redis using key patterns for quick access.
  • πŸ“¬ Configured RabbitMQ consumers to handle bulk background processing.

Result: The app performance improved. Traffic spikes no longer caused downtime, teams could work independently, and new features were released faster with reduced risk.

βœ… 2. Fixing a Crash During a Flash Sale

Challenge: During a high-traffic flash sale, the app crashed. CPU usage hit 100% and key features like login and checkout stopped working due to a memory leak in async operations.

Solution:

  • βš™οΈ Quickly scaled out horizontally by adding more servers to handle the traffic.
  • πŸ•΅οΈ Identified the root cause β€” improper use of Promise.all running thousands of operations simultaneously.
  • 🧯 Applied a patch to limit concurrency using a task queue strategy.
  • πŸ“ˆ Set up alerts and dashboards using New Relic to monitor system health in real time.
  • πŸ“š Trained the dev team on better async programming practices.

Implementation:

  • πŸ” Rewrote critical async flows using controlled concurrency (e.g., p-limit or custom batching).
  • πŸ“Š Integrated New Relic with alerts for CPU, memory, and error rates.
  • 🧠 Held internal sessions for better async error handling and performance practices.

Result: System restored within 30 minutes. Future sales ran smoothly, and the engineering team became more resilient and knowledgeable.

🀝 How do you resolve conflicts in a team setting?

  • I am trying to address the issue early and focusing on open respectful communication.
  • I make sure I fully understand both perspectives by listening carefully to everyone involved.
  • I am trying to avoid jumping to conclusions and instead try to identify the root causeβ€”whether it’s a misunderstanding, a difference in priorities, or unclear responsibilities.
  • Once I have clarity, I bring the parties together and make conversation, focusing on common goals rather than personal opinions.
  • If needed, I involve a senior or manager to mediate, but I usually aim to resolve it within the team itself.
  • At the end of the day, I remind everyone that we’re all working toward the same outcomeβ€”delivering great workβ€”and collaboration is key to that.

πŸ‘¨β€πŸ« Give an example of a time you mentored a junior developer.

πŸ•’ How do you ensure on-time delivery and code quality in a sprint cycle?

🀝 What is your approach to code reviews and collaboration?

πŸ”₯ How do you manage pressure from stakeholders and tight deadlines?

🌟 How do you keep your team motivated during low-morale phases?

🧠 What is a Generator?

A Generator is a special type of function in JavaScript that you can pause and resume. Unlike normal functions that run from start to end immediately, generators let you produce values one at a time β€” perfect for handling sequences or large data in chunks.

πŸ“¦ How to Use It?


	// Define a generator function
	function* greetings() {
	  yield "Hi";
	  yield "How are you?";
	  yield "Bye";
	}

	// Create generator object
	const gen = greetings();

	console.log(gen.next()); // { value: 'Hi', done: false }
	console.log(gen.next()); // { value: 'How are you?', done: false }
	console.log(gen.next()); // { value: 'Bye', done: false }
	console.log(gen.next()); // { value: undefined, done: true }
		

Each time you call gen.next(), it resumes the generator and runs until the next yield.

When there are no more values to yield, done: true is returned.

⏱️ Debouncing & Throttling

🧘 Debouncing


function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage
window.addEventListener('resize', debounce(() => {
  console.log('Resized!');
}, 300));
  

πŸƒ Throttling


function throttle(fn, limit) {
  let inThrottle;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Usage
window.addEventListener('scroll', throttle(() => {
  console.log('Scrolled!');
}, 200));
  

βš–οΈ Difference: some() vs every()

Feature some() every()
Definition Returns true if at least one element satisfies the condition. Returns true if all elements satisfy the condition.
Stops when It finds the first match. It finds the first failure.
Return value true / false true / false
Common use Checking if any items meet a condition (e.g., "Is there at least one error?") Checking if all items meet a condition (e.g., "Are all fields valid?")
Example
[1, 2, 3].some(n => n > 2)
// true
[1, 2, 3].every(n => n > 0)
// true

πŸ” some() Method

Definition: Returns true if at least one element satisfies the condition.

Stops when: It finds the first match.

Return value: true / false

Common use: Checking if any items meet a condition (e.g., "Is there at least one error?")

Example:

[1, 2, 3].some(n => n > 2)
// true

πŸ” every() Method

Definition: Returns true if all elements satisfy the condition.

Stops when: It finds the first failure.

Return value: true / false

Common use: Checking if all items meet a condition (e.g., "Are all fields valid?")

Example:

[1, 2, 3].every(n => n > 0)
// true

βœ… Summary

πŸ” Array.find() & Array.findIndex()

βœ… Array.prototype.find()


const numbers = [4, 9, 16, 25];
const found = numbers.find(num => num > 10);
console.log(found); // 16
  

πŸ“ Array.prototype.findIndex()


const items = ['apple', 'banana', 'cherry'];
const index = items.findIndex(fruit => fruit.startsWith('b'));
console.log(index); // 1
  

🧠 Use Cases

πŸ—οΈ express-session Overview

βœ… What express-session Does:

πŸ”„ Typical Flow in a Login System:

  1. Client sends a login request to /login.
  2. Server validates user credentials.
  3. If login is successful:

// Server-side code
req.session.user = { id: user._id, name: user.name };
  

🧾 Server sends back a response with a cookie:


Set-Cookie: connect.sid=somerandomsessionid
  

	const express = require('express');
	const session = require('express-session');

	const app = express();
	const PORT = 3000;

	// Setup session middleware
	app.use(session({
	  secret: 'your_secret_key',            // πŸ—οΈ Secret for signing the session ID cookie
	  resave: false,                         // Don't save session if unmodified
	  saveUninitialized: true,              // Save new sessions
	  cookie: { maxAge: 60000 }             // Session expires in 1 minute
	}));

	// Set a value in session
	app.get('/set', (req, res) => {
	  req.session.username = 'john_doe';
	  res.send('Session value set!');
	});

	// Get the value from session
	app.get('/get', (req, res) => {
	  const username = req.session.username;
	  res.send(`Username in session is: ${username}`);
	});

	// Destroy the session
	app.get('/logout', (req, res) => {
	  req.session.destroy(err => {
		if (err) return res.send('Error logging out.');
		res.send('Logged out and session destroyed!');
	  });
	});

	app.listen(PORT, () => {
	  console.log(`Server is running at http://localhost:${PORT}`);
	});


πŸ’‘ Bonus Tip:

Always use app.use(session({...})) early in your middleware stack to ensure req.session is available for all routes.

πŸ”„ Control Flow in Node.js

Control flow in Node.js means the order in which your code runs. Because Node.js uses non-blocking and event-based programming, things don’t always run one after the other. So, it's important to manage the flow of your code to make sure things happen in the right order and any errors are handled properly.

🎯 Why It Matters

🧰 Control Flow Mechanisms

βš™οΈ Synchronous vs Asynchronous Flow


// Synchronous Example
console.log('1');
console.log('2');
console.log('3');
// Output: 1, 2, 3

// Asynchronous Example
console.log('Start');

setTimeout(() => {
  console.log('Timeout Finished');
}, 1000);

console.log('End');
// Output: Start, End, Timeout Finished
  

πŸ“š Example of All Three Styles


// Callback style
fs.readFile('file.txt', (err, data) => {
  if (err) throw err;
  console.log(data.toString());
});

// Promise style
fs.promises.readFile('file.txt')
  .then(data => console.log(data.toString()))
  .catch(err => console.error(err));

// Async/Await style
async function readFileAsync() {
  try {
    const data = await fs.promises.readFile('file.txt');
    console.log(data.toString());
  } catch (err) {
    console.error(err);
  }
}
readFileAsync();
  

πŸ” Summary

🌐 Ajax vs Node.js

πŸ”Ή What is Ajax?

Ajax stands for Asynchronous JavaScript and XML. It is a technique used in web development to make requests to the server without reloading the page. This allows for a smoother, more interactive user experience.

Common uses of Ajax include:

  • Fetching new data from a server without refreshing the page
  • Sending data to a server in the background (e.g., form submissions)
  • Updating parts of a webpage dynamically (e.g., live search results)

πŸ”Ή What is Node.js?

Node.js is a runtime environment that allows you to run JavaScript on the server side. It’s built on Chrome’s V8 JavaScript engine and is non-blocking, meaning it can handle multiple operations at once without waiting for one to finish before starting another.

Common uses of Node.js include:

  • Building server-side applications (e.g., APIs, web servers)
  • Handling asynchronous operations (e.g., reading files, making HTTP requests)
  • Real-time applications (e.g., chat apps, live updates)

βš–οΈ Key Differences

Aspect Ajax Node.js
Type Client-side technique Server-side runtime
Purpose Fetch and send data asynchronously between client and server Run JavaScript on the server and build backend applications
Role Improves frontend user experience Handles server-side logic and requests
Use Case Dynamic web pages, live data updates, non-refreshing web apps Backend services, APIs, real-time apps

πŸ”‘ Summary

  • Ajax is a technique for fetching data from a server without reloading the page, mainly used in the browser.
  • Node.js is a runtime that allows you to run JavaScript on the server side, enabling backend operations and real-time features.

🌐 SAFE

SAFe, or the Scaled Agile Framework, is a methodology designed to help large organizations apply Agile practices across multiple teams and departments in a coordinated way. While Agile works great for small teams, SAFe scales those principles so that many teams can work together efficiently toward common business goals."

How to summarize Agile and SAFe experience on a resume or interview?

"I have experience working in Agile environments following SAFe methodology, where I participated in cross-team PI planning, collaborated within Agile Release Trains, and contributed to incremental delivery of features aligned with business goals. I’m familiar with SAFe ceremonies such as PI planning, system demos, and Inspect & Adapt workshops, and have worked closely with Product Owners and Scrum Masters to ensure continuous delivery and alignment with stakeholders."

DUAL Commit in Node.js

In Node.js, Dual Commit refers to the pattern of performing a single logical operation that needs to be persisted in two different systems at the same time, such as writing to a database and sending an event to a message queue, or updating two separate databases.

Why It’s Challenging

  • Atomicity: Node.js doesn’t provide built-in distributed transactions, so both writes must succeed together.
  • Failure Handling: If one system fails, you must decide whether to roll back or retry.
  • Data Consistency: Without careful design, the two systems can become out of sync.

Common Solutions

  • Two-Phase Commit (2PC): Coordinating both systems to agree before final commit β€” heavy and less common in Node.js.
  • Outbox Pattern: First write to a single β€œsource of truth” (e.g., DB), then publish events from there asynchronously.
  • Idempotent Operations: Ensure retries won’t cause duplicate effects.

Example


// Example: Save order in DB + send message to Kafka
async function dualCommit(orderData) {
    try {
        await db.collection('orders').insertOne(orderData);
        await kafkaProducer.send({
            topic: 'orders',
            messages: [{ value: JSON.stringify(orderData) }]
        });
    } catch (err) {
        console.error('Dual commit failed:', err);
        // Retry logic or rollback here
    }
}