Home / Articles

Six Integration Patterns for Connecting Node.js Services Reliably

Learn the core patterns behind robust Node.js integrations: request-response, polling, webhooks, API key, JWT and OAuth auth, retries with backoff, and data mapping.

Publisher: REACTAPP.TOP7 min read1511 words

This article is published in English.

Sources: 6 API Integration Patterns Every Node.js Integration Engineer Must Know

Sending an HTTP request is easy; keeping an integration working when the other system is slow, down or shaped differently is the real job. This guide covers six patterns that recur when Node.js services talk to payment providers, CRMs, ERPs and each other, plus the pitfalls that separate a demo from a dependable integration.

1. Request-response: the synchronous baseline

In the request-response model, a client sends a request, the server does the work, and the answer comes back in the same connection. It covers most everyday operations: signing in, creating or updating records, fetching a customer, deleting a resource.

app.get("/users/:id", async (req, res) => {
  const user = await db.findUser(req.params.id);
  res.json(user);
});

It is simple to debug and the default for CRUD. Its weakness is long-running work: a large report or big file can exceed client or proxy timeouts. Also note that this handler returns null with status 200 when the user does not exist; a real endpoint should answer 404.

2. Polling: asking until the job is done

When a result cannot be produced immediately, the server can accept the job, hand back an identifier, and let the client check back. Typical cases include waiting for a payment to settle, a report to render, a video to transcode or an order to ship.

Starting a job returns its ID:

POST /reports

Response:
{
  "jobId": "abc123"
}

The client then checks the status endpoint at intervals:

GET /reports/abc123/status

until the job reports that it has finished:

{
  "status": "completed"
}

Polling is simple and firewall-friendly, so many enterprise systems still rely on it. The cost is wasted traffic, since most checks return "not yet". Space out requests (honoring Retry-After if sent), set an overall deadline, and handle failed as well as completed.

3. Webhooks: letting the other side call you

Webhooks flip the direction. Instead of your service asking repeatedly, the provider sends an HTTP request to your endpoint when something happens: a payment succeeds, an order is placed, a message arrives, a customer registers, stock changes. Stripe, Slack, GitHub and Shopify all lean heavily on this model.

A minimal receiver in Express:

app.post("/webhook/payment", (req, res) => {
  const event = req.body;

  if (event.type === "payment.success") {
    console.log("Payment received!");
  }

  res.sendStatus(200);
});

This shows the shape, but a production receiver needs more:

  • Verify the sender. Anyone can post to a public URL. Providers typically sign each payload, and you must check that signature (usually against the raw request body) before trusting the event.
  • Expect duplicates. Deliveries are generally retried until acknowledged, so the same event can arrive more than once. Store processed event IDs and make handlers idempotent.
  • Acknowledge fast. Return a 2xx quickly and push slow work onto a queue; otherwise the provider may time out and resend.

Event-driven integration is increasingly preferred because it removes polling traffic.

4. Authentication: proving who is calling

Every integration starts with credentials. Three approaches cover most cases.

API keys

The simplest scheme sends a shared secret in a header:

GET /users

x-api-key: abc123xyz

From Node.js with axios, the key should come from configuration, never from source code:

axios.get(url, {
  headers: {
    "x-api-key": process.env.API_KEY
  }
});

API keys suit internal tools and straightforward server-to-server integrations. They carry no expiry or user identity by themselves, so rotate them and scope them as narrowly as the provider allows.

JSON Web Tokens

JWTs enable stateless authentication. The usual sequence:

  1. The user signs in.
  2. The server issues a signed token.
  3. The client keeps the token.
  4. Each later request carries it in a header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Issuing one in Node.js:

const token = jwt.sign(
  { userId: 1 },
  process.env.JWT_SECRET
);

Any service with the verification key can validate the token without a session store, which suits microservices. The catch is revocation: a token stays valid until it expires, so always set expiresIn, which this snippet omits.

OAuth 2.0

OAuth 2.0 is what makes delegated access work: "Sign in with Google", Facebook login, Microsoft enterprise single sign-on, CRM connectors and any third-party app acting on a user's behalf. In the common authorization code flow:

  1. The user approves the requested access at the provider.
  2. The provider redirects back with a short-lived authorization code.
  3. Your server exchanges that code for an access token.
  4. API calls are made with the access token.

Worth learning in depth: access and refresh tokens, the Authorization Code flow, the Client Credentials flow for machine-to-machine calls, PKCE, and OpenID Connect, an identity layer on top of OAuth.

5. Resilience: designing for failure

Networks time out and dependencies go down. Reliable integrations assume this will happen.

Retrying failed calls

A basic retry loop:

for (let i = 0; i < 3; i++) {
  try {
    return await axios.get(url);
  } catch (error) {
    console.log("Retrying...");
  }
}

As written, it retries immediately with no pause, and if all three attempts fail it returns undefined instead of throwing the last error, so the caller cannot tell what went wrong. Rethrow after the final attempt, and retry only transient failures (network errors, timeouts, 5xx, 429), not 4xx errors that will fail identically every time. Be careful retrying non-idempotent requests such as payments unless the API supports idempotency keys.

Timeouts

A request without a timeout can hang indefinitely and hold resources while it does. Always set one:

axios.get(url, {
  timeout: 5000
});

Exponential backoff

Retrying at a fixed interval keeps hammering a struggling service:

1s
1s
1s
1s

Doubling the wait each time gives it room to recover:

1s
2s
4s
8s
16s

The calculation is a one-liner:

const delay = attempt =>
  Math.pow(2, attempt) * 1000;

Cap the maximum delay, and add random jitter so that many clients failing at once do not retry in lockstep.

Circuit breakers

A circuit breaker counts failures to a downstream service and, past a threshold, stops calling it for a cool-down period before sending a trial request. This guards against cascading failures, resource exhaustion and system-wide outages. Opossum is a common choice in Node.js; Resilience4j fills the role in Java, and Hystrix appears in older systems.

6. Data transformation: translating between systems

Two systems rarely agree on data shape, so a large part of integration work is translation.

Field mapping

One system uses camelCase:

{
  "firstName": "John"
}

another expects snake_case:

{
  "first_name": "John"
}

A small mapper bridges them:

function transform(user) {
  return {
    first_name: user.firstName
  };
}

Pure mapper functions are easy to unit-test and keep provider quirks out of business logic.

JSON and XML

Many legacy systems still speak XML while newer services produce JSON, so an integration layer often converts between the two in both directions.

Dates

One side may send ISO format:

2026-06-26

while the other expects day-first notation:

26/06/2026

Convert explicitly, and be clear about time zones when values include times; a date shifted across midnight is a classic integration bug.

Currency amounts

One service reports a decimal amount:

{
  "amount": 10.50
}

the other wants integer minor units:

{
  "amount_cents": 1050
}

Floating-point values cannot represent every decimal exactly, so round when converting (for example Math.round(amount * 100)) and prefer integer cents internally.

How the patterns combine in practice

A typical enterprise layout puts a Node.js gateway in front of several backends:

Frontend
   |
Node.js API Gateway
   |
   +-- Authentication Service
   +-- Payment Service
   +-- CRM Service
   +-- ERP Systems
   +-- Notification Service

These connections combine the patterns above: request-response APIs, JWT and OAuth, webhooks, retries and breakers, transformation layers, plus monitoring and logging. For engineers moving into integration work, the areas that pay off first are REST APIs, webhooks, JWT, OAuth 2.0, data transformation and event-driven design, followed by retry and resilience techniques, then polling and circuit breakers.

Key takeaways

  • Use request-response for quick operations; switch to polling or webhooks when work takes time.
  • Verify webhook signatures, deduplicate events and acknowledge quickly.
  • Keep credentials in configuration, give tokens an expiry, and learn the OAuth flows properly.
  • Retry only transient failures, with timeouts, capped exponential backoff and jitter.
  • Isolate data mapping in tested pure functions, and treat dates and money with extra care.