If you’ve ever built an app that talks to other services—an API, a database, or a third-party provider—you’ve probably faced this:
Everything works fine until that external service slows down… or worse, goes offline. Suddenly your app is stuck, retrying requests that will never succeed. Users wait forever. Your logs explode. Your server eats CPU like it’s at an all-you-can-eat buffet.
That’s where the Circuit Breaker pattern comes in.
What is a Circuit Breaker?
Think of it like the electrical breaker in your house. If there’s too much load, the breaker snaps to prevent damage. You can’t use it until it’s reset.
In software, a circuit breaker works the same way:
- If a service is failing too often, the breaker “opens” and stops sending requests for a while.
- After a cooldown period, it tries again with a few requests.
- If the service recovers, the breaker “closes” and things go back to normal.
This protects your app from wasting resources on doomed requests and makes failures more graceful.
Building a Simple Circuit Breaker in Node.js
Let’s build a lightweight Circuit Breaker around a Fastify route that calls an external API.
Step 1: Create a Breaker Class
We’ll start with a small utility class.
// circuitBreaker.js
class CircuitBreaker {
constructor({ failureThreshold = 3, cooldownPeriod = 10000, successThreshold = 2 }) {
this.failureThreshold = failureThreshold; // how many failures before "open"
this.cooldownPeriod = cooldownPeriod; // how long to wait before "half-open"
this.successThreshold = successThreshold; // how many successes before "close"
this.state = "CLOSED"; // CLOSED -> OPEN -> HALF-OPEN
this.failures = 0;
this.successes = 0;
this.nextTry = Date.now();
}
async call(action) {
if (this.state === "OPEN") {
if (Date.now() > this.nextTry) {
this.state = "HALF-OPEN"; // let a request through
} else {
throw new Error("Circuit breaker is OPEN");
}
}
try {
const result = await action();
if (this.state === "HALF-OPEN") {
this.successes++;
if (this.successes >= this.successThreshold) {
this.state = "CLOSED";
this.failures = 0;
this.successes = 0;
}
}
return result;
} catch (err) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = "OPEN";
this.nextTry = Date.now() + this.cooldownPeriod;
}
throw err;
}
}
}
module.exports = CircuitBreaker;
This little class tracks failures, switches states, and decides when to block requests.
Step 2: Use It in a Fastify App
Now let’s integrate it into a Fastify service that calls a flaky external API.
'use strict';
const Fastify = require('fastify');
const axios = require('axios');
const CircuitBreaker = require('./circuitBreaker');
const app = Fastify({ logger: true });
// Create a breaker for our external API
const apiBreaker = new CircuitBreaker({
failureThreshold: 3,
cooldownPeriod: 5000,
successThreshold: 2,
});
app.get('/joke', async (req, reply) => {
try {
const result = await apiBreaker.call(async () => {
// Example flaky API
const { data } = await axios.get('https://official-joke-api.appspot.com/random_joke');
return data;
});
return { success: true, joke: result };
} catch (err) {
app.log.error(err.message);
return { success: false, message: "Service is temporarily unavailable. Try again later." };
}
});
app.listen({ port: 3000 });
How It Works (Step by Step)
- User hits
/joke
. - The breaker forwards the call to the joke API.
- If the API fails 3 times in a row → breaker goes OPEN.
- While open, requests instantly fail with a friendly message instead of waiting forever.
- After 5 seconds, breaker goes HALF-OPEN: it lets a few requests try again.
- If those succeed → breaker closes. If not → back to OPEN.
Why This Matters
Without a circuit breaker:
- Your app might keep retrying dead services.
- Users see long timeouts.
- Your servers waste CPU and threads.
With a circuit breaker:
- Failures are fast and predictable.
- Your system gets time to recover.
- You protect the rest of your app from cascading crashes.
Real-World Tips
- Use it for external APIs, databases, or any “unreliable” dependency.
- Tune thresholds carefully: too strict, and you block a service that’s just a little slow; too lenient, and you don’t protect yourself enough.
- Combine it with retries + exponential backoff for a full resilience strategy.
- Consider libraries like opossum if you need production-grade breakers with monitoring.
Wrapping Up
The Circuit Breaker pattern is one of those unsung heroes of system design. It doesn’t make your app faster, but it makes it more resilient—and when things go wrong (which they will), your users will thank you for it.
With just a few lines of Node.js and Fastify code, you can protect your app from cascading failures and keep things running smoothly, even when the world outside your server is a little chaotic.
You can find the complete code here: https://github.com/martina-blog/tutorials
Leave a Reply