Building distributed systems in Node.js often feels like walking a tightrope.
One slow database query or a flickering third-party API can cascade into a complete system outage. While there are heavy frameworks out there to handle these issues, sometimes you just want a clean, simple tool that stays out of your way.
Enter Resilia.
I created Resilia to solve a specific problem: adding professional-grade resilience patterns to TypeScript applications without the bloat.
It is a zero-dependency, decorator-based library that wraps your critical methods in a protective layer of stability.
If you are tired of writing repetitive try-catch-retry logic or manually managing concurrency limits, this tool is for you.
The Problem: Fragile Architectures
In a microservices environment, failures are not a matter of "if," but "when." Network packets get lost, services get overloaded, and databases time out.
Without a proper resilience strategy, a single failing component can exhaust your thread pool or connection limit, bringing your entire application to a halt.
To prevent this, you typically need three things:
- Circuit Breakers to stop calling a failing service.
- Retries to handle temporary blips.
- Bulkheads to limit how many requests run in parallel.
Wiring these up manually for every function is tedious and error-prone. Resilia automates this entire stack.
Meet Resilia
Resilia is designed around the "Matryoshka" security model—a three-layered approach to protecting your code execution. It uses TypeScript decorators to apply these layers declaratively.
You do not need to wrap your code in callbacks or complex configuration objects. You simply tag your method, and Resilia handles the rest.
Layer 1: The Circuit Breaker
This is your outer defense. It acts like a safety switch in your electrical panel. If a service starts failing consistently (for example, exceeding a 50% error rate), the circuit "trips" and opens.
When open, Resilia immediately rejects new requests without even trying to execute the underlying function. This prevents your system from wasting resources on a dead service and gives the failing subsystem time to recover.
After a set sleep window, it allows a single test request through (Half-Open state) to check if health has been restored.
Layer 2: Intelligent Retries
Middle-layer protection handles transient errors—those one-off network glitches that vanish if you just try again. However, blind retries can lead to a "thundering herd" problem where your retries D-DoS your own server.
Resilia uses an Exponential Backoff strategy with Jitter. It waits longer between each attempt and adds a bit of randomness to the timing.
This ensures that if multiple clients fail at once, they do not all hit the server again at the exact same millisecond.
Layer 3: The Bulkhead
The inner layer is about resource isolation. It limits the number of concurrent executions for a specific method. If you have a heavy reporting query, you can restrict it to 5 simultaneous runs.
Additional requests queue up until a slot opens. This guarantees that one slow feature never hogs all your server's CPU or memory.
How to Use It
The best part of Resilia is the developer experience. It requires minimal setup.
First, install the package:
npm install resilia reflect-metadata
Then, use the @Resilient decorator on any class method:
import { Resilient } from 'resilia';
class PaymentService {
@Resilient({
concurrency: 5, // Max 5 simultaneous requests
queue: 10, // Max 10 waiting in line
maxRetries: 3, // Try 3 times before failing
errorThreshold: 0.5, // Trip circuit if >50% fail
sleepWindowMs: 30000 // Rest for 30s when tripped
})
async processTransaction(id: string) {
// Your critical business logic here
return await db.payments.create({ id });
}
}
That is it. Your processTransaction method is now protected by a circuit breaker, a bulkhead, and a smart retry mechanism.
Observability Built-In
You cannot improve what you cannot measure. Resilia is built with observability in mind. Every component is an event emitter, allowing you to hook into state changes and metrics in real-time.
You can listen for circuit state changes (like going from Closed to Open) or track when the bulkhead queue rejects a request. This makes it trivial to integrate with monitoring tools like Prometheus or Grafana.
import { resilienceRegistry } from 'resilia';
// Monitor your application health
resilienceRegistry.forEach(({ breaker, bulkhead }, key) => {
breaker.on('state:changed', (event) => {
console.log(`Circuit ${key} changed state to ${event.to}`);
});
});
Why Resilia?
-
Zero Dependencies: It keeps your
node_moduleslight and your security footprint small. - Performance: It is lightweight and designed to add negligible overhead to your calls.
- TypeScript Native: It leverages decorators for a clean, modern syntax.
Support the Project
I built Resilia to make robust Node.js development accessible to everyone. If you find this tool useful or if the code helps you learn more about resilience patterns, please consider supporting the project.
Check out the code and give it a Star on GitHub:
https://github.com/Silent-Watcher/resilia
Your stars help others discover the tool and motivate me to keep adding features like rate limiting and advanced timeout strategies.
💡 Have questions? Drop them in the comments!
Top comments (1)
Thanks for taking the time to read through this overview of Resilia.