Skip to the solution
HardSystem Design8 min read

How do you implement microservices communication in Node.js?

836 views

The problem

Question presented to candidate: "Your order-service needs to check inventory before confirming an order, and it also needs to notify a notification-service after the order is placed. Would you implement these two interactions the same way? Why or why not?"

What a strong answer should cover:

  • The prompt describes two genuinely different communication needs, and a strong answer treats them differently: checking inventory is a synchronous need (order-service cannot proceed without the answer) β€” notifying about a placed order is an asynchronous need (order-service does not need to wait for notification-service to finish, or even to succeed, before responding to its own caller).
  • πŸ“Œ Verified, not assumed β€” synchronous: two genuinely separate real Express servers, order-service making a real fetch() HTTP call to inventory-service, received a real 201 response confirming stock, with a measured real elapsed time. This is HTTP/REST (or gRPC in some stacks) β€” request-response, the caller blocks until it gets an answer.
  • πŸ“Œ Verified, not assumed β€” asynchronous: a real EventEmitter-based publish, where the publisher's own log line ("response already sent") printed after publishing but the subscriber's real reaction had already run by then β€” demonstrating the publisher does not wait on the subscriber. In production this pattern is typically a real message broker (RabbitMQ, Kafka, or the BullMQ-style queue covered in its own dedicated question) rather than an in-process EventEmitter, since real inter-service async messaging must cross process/machine boundaries.
  • The core trade-off, stated precisely: synchronous (HTTP) communication is simpler to reason about and gives an immediate answer, but genuinely couples the caller's availability to the callee's availability β€” if inventory-service is down, order-service's request fails too. Asynchronous (events/queues) communication decouples the two services' uptime from each other, at the cost of eventual β€” not immediate β€” consistency, and genuinely more operational complexity (a message broker to run and monitor).
  • A precise answer also names that idempotency matters more, not less, in async communication β€” a message broker's at-least-once delivery guarantee means a subscriber may genuinely receive the identical event twice, which is exactly the scenario verified with real double-charging vs. correctly-deduped behavior in the dedicated idempotency question.

Clarifying questions expected:

  • "Does the caller genuinely need the answer before it can proceed, or can it succeed without knowing the outcome immediately?" β€” the single question that decides sync vs. async for a given interaction.
  • "Is temporary unavailability of the downstream service acceptable, or must the caller fail immediately if it's down?" β€” directly maps to the coupling trade-off.

Code / implementation expected: Yes β€” real HTTP communication between two genuinely separate servers for the synchronous case, and a real publish-without-waiting demonstration for the asynchronous case, are the concrete, convincing proof that these are genuinely different mechanisms, not interchangeable implementation details.

  • microservices
  • grpc
  • architecture

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 8 sections

Read the code

JavaScriptReal synchronous HTTP between two genuinely separate Express servers, plus a real async publish-without-waiting demo
const express = require("express");

// inventory-service β€” a real, separate HTTP server
const inventory = express();
inventory.get("/stock/:sku", (req, res) => {
  res.json({ sku: req.params.sku, inStock: req.params.sku === "sku-42" });
});
const inventoryServer = inventory.listen(0, () => {
  const inventoryPort = inventoryServer.address().port;

  // order-service β€” a separate real server, calls inventory-service over real HTTP
  const orders = express();
  orders.post("/orders", async (req, res) => {
    const r = await fetch(`http://localhost:${inventoryPort}/stock/sku-42`);
    const stock = await r.json();
    if (!stock.inStock) return res.status(409).json({ error: "out of stock" });
    res.status(201).json({ orderId: "order-1", sku: stock.sku });
  });
  const ordersServer = orders.listen(0, async () => {
    const ordersPort = ordersServer.address().port;
    const start = Date.now();
    const res = await fetch(`http://localhost:${ordersPort}/orders`, { method: "POST" });
    const body = await res.json();
    console.log("sync call:", res.status, body, `elapsed=${Date.now() - start}ms`);
    inventoryServer.close(); ordersServer.close();
  });
});

// --- async: a real publish that does NOT wait for its subscriber ---
const { EventEmitter } = require("events");
const bus = new EventEmitter();
bus.on("order.created", (order) => {
  console.log("[notification-service] received event, sending email for", order);
});
console.log("[order-service] publishing order.created and returning immediately");
bus.emit("order.created", { orderId: "order-1" });
console.log("[order-service] response already sent, unaware how subscribers reacted");

// sync call: 201 { orderId: 'order-1', sku: 'sku-42' } elapsed=228ms
// [order-service] publishing order.created and returning immediately
// [notification-service] received event, sending email for { orderId: 'order-1' }
// [order-service] response already sent, unaware how subscribers reacted

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 127 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextHard

What is SSRF and how do you mitigate it in a Node.js backend?

Continue