DEV Community

Sachin Kasana
Sachin Kasana

Posted on • Originally published at sachinkasana.Medium on

Retry Logic in Node.js: How to Handle Flaky APIs Without Losing Your Mind

“We’ve all been there. Your Node.js app talks to a payment gateway or third-party API, and everything works… until it doesn’t. One network hiccup, and your app fails hard.”

Real Problem:

  • External APIs might:
  • Rate-limit you
  • Drop connections randomly
  • Throw 500s under load
  • A retry mechanism can prevent unnecessary app failures.

💥 Naive Retry Example (Don’t Do This)

function fetchWithRetry(url, attempts = 3) {
  return fetch(url).catch(err => {
    if (attempts <= 1) throw err;
    return fetchWithRetry(url, attempts - 1);
  });
}
Enter fullscreen mode Exit fullscreen mode

What’s wrong here?

  • No delay between retries.
  • Immediate hammering worsens server load.
  • No distinction between fatal vs retryable errors.

🧠 Retry Best Practices

🔧 Using axios-retry (Easiest)

 npm install axios axios-retry

const axios = require('axios');
const axiosRetry = require('axios-retry');

axiosRetry(axios, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return error.response?.status >= 500 || error.code === 'ECONNABORTED';
  }
});

axios.get('https://flaky.api.com/data')
  .then(res => console.log(res.data))
  .catch(err => console.error('Request failed after retries', err));
Enter fullscreen mode Exit fullscreen mode

🛠 Using p-retry for Custom Logic

npm install p-retry

const pRetry = require('p-retry');
const fetch = require('node-fetch');

const fetchData = async () => {
  const res = await fetch('https://unstable-api.com/data');
  if (!res.ok) throw new Error(`Status: ${res.status}`);
  return res.json();
};

pRetry(fetchData, {
  retries: 5,
  onFailedAttempt: error => {
    console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
  }
}).then(console.log).catch(console.error);
Enter fullscreen mode Exit fullscreen mode

⚠️ Retry-Aware Design

  • Always use idempotent methods (GET, PUT) for retries.
  • POST requests can cause duplicate side effects if retried without safeguards (e.g., charge twice).

💬 Logging and Monitoring

onFailedAttempt: error => {
  log.warn({
    attempt: error.attemptNumber,
    message: error.message
  });
}
Enter fullscreen mode Exit fullscreen mode
  • Helps in debugging production failures.

📦 Bonus: Retry with Queues (Bull/Agenda)

For longer retry flows:

  • Use bull (Redis-based job queue) to retry in background.
  • Useful for payment retries, webhook delivery, etc.

🔚 Conclusion

“Retries are like seatbelts for your API calls. You hope you don’t need them — but when you do, they can save your system from disaster.”

Encourage readers to:

  • Use libraries, not reinvent the wheel.
  • Always handle retries thoughtfully , not blindly.

Optional Enhancements

  • Retry + Circuit Breaker (use opossum)
  • Retry + Metrics for success/failure tracking
  • Custom retry decorator function

I’m Sachin Kasana — Principal Engineer, full-stack enthusiast, and open-source contributor. I write practical dev blogs to make backend scaling, performance, and clean code simpler for everyone.

📬 Enjoyed this post?

Follow me on Medium for more hands-on backend, Node.js, and architecture articles every week.

Top comments (0)