Table of Contents
- The Problem That Started It All
- Enter Rule Engine JS
- Real-World Examples
- Built for Production
- Framework Agnostic
- Getting Started (30 Seconds)
- Why I Think You'll Love It
- What's Next?
Have you ever found yourself buried in nested if-else
statements, trying to implement complex business rules that change every other week? Or worse, watched your clean codebase turn into a maintenance nightmare because business logic was scattered across dozens of files?
I've been there. And that's exactly why I built Rule Engine JS.
The Problem That Started It All
Picture this: You're building an e-commerce platform, and the business team comes to you with "simple" requirements:
- VIP customers get discounts on orders over $100
- Regular customers need 1000+ loyalty points for discounts
- First-time customers get 20% off orders over $50
- But only during business hours
- Unless it's a weekend
- Or a holiday
- And the rules change next month...
Sound familiar? 😅
The real kicker came when they said: "Oh, and we need to store these rules in the database so we can update them without deploying."
That's when I realized I needed something that could handle JSON-serializable business logic without sacrificing developer experience or performance.
Enter Rule Engine JS
After trying existing solutions and finding them either too complex, too slow, or lacking key features, I decided to build something better. Here's what makes Rule Engine JS different:
🚀 Zero Dependencies, Maximum Performance
No bloated dependency trees. No security vulnerabilities from third-party packages. Just clean, efficient JavaScript that works everywhere:
import { createRuleEngine, createRuleHelpers } from 'rule-engine-js';
const engine = createRuleEngine();
const rules = createRuleHelpers();
// That's it. You're ready to go.
🎯 Developer Experience That Actually Makes Sense
Remember those nested JSON rules that make your eyes bleed? Not here:
// Instead of this nightmare:
const rule = {
"and": [
{ "gte": ["user.age", 18] },
{ "eq": ["user.status", "active"] },
{ "in": ["write", "user.permissions"] }
]
}
// Write this:
const rule = rules.and(
rules.gte('user.age', 18),
rules.eq('user.status', 'active'),
rules.in('write', 'user.permissions')
);
Both compile to the same JSON (perfect for database storage), but only one is actually maintainable.
Dynamic Field Comparison (The Game Changer)
Here's where things get interesting. Need to compare fields within your data? Most rule engines make you jump through hoops. Rule Engine JS makes it natural:
const orderValidation = rules.and(
rules.field.lessThan('order.total', 'user.creditLimit'),
rules.field.equals('order.currency', 'user.preferredCurrency'),
rules.gte('user.accountAge', 30) // days
);
This was the feature I desperately needed for my original project - and now it's built right in.
Real World Examples
Let's solve that e-commerce discount problem from earlier:
const discountEligibility = rules.or(
// VIP customers with minimum order
rules.and(
rules.eq('customer.type', 'vip'),
rules.gte('order.total', 100)
),
// High loyalty points
rules.gte('customer.loyaltyPoints', 1000),
// First-time customer bonus
rules.and(
rules.isTrue('customer.isFirstTime'),
rules.gte('order.total', 50)
)
);
// Later, when business rules change:
const updatedRules = await database.getRules('discount-eligibility');
const result = engine.evaluateExpr(updatedRules, customerData);
No deployments. No code changes. Just update the database and you're done.
Form Validation Made Simple
const formValidation = rules.and(
rules.validation.required('email'),
rules.validation.email('email'),
rules.field.equals('password', 'confirmPassword'),
rules.validation.ageRange('age', 18, 120),
rules.isTrue('agreedToTerms')
);
// Returns detailed validation results
const result = engine.evaluateExpr(formValidation, formData);
if (!result.success) {
console.log('Validation failed:', result.error);
}
User Access Control
const accessRule = rules.and(
rules.isTrue('user.isActive'),
rules.or(
rules.eq('user.role', 'admin'),
rules.and(
rules.eq('user.department', 'resource.department'),
rules.in('read', 'user.permissions')
)
)
);
// Perfect for middleware
app.get('/api/sensitive-data', (req, res, next) => {
const hasAccess = engine.evaluateExpr(accessRule, {
user: req.user,
resource: { department: 'finance' }
});
hasAccess.success ? next() : res.status(403).json({ error: 'Access denied' });
});
Built for Production
Performance That Scales
- Intelligent LRU caching for repeated evaluations
- Path resolution caching for nested object access
- Regex pattern caching for validation rules
- Typically under 1ms evaluation time
Security First
- Built-in protection against prototype pollution
- Safe path resolution (no function execution)
- Input validation and sanitization
- Configurable complexity limits to prevent DoS
Monitoring Included
// Built-in performance metrics
const metrics = engine.getMetrics();
console.log({
evaluations: metrics.evaluations,
cacheHitRate: metrics.cacheHits / metrics.evaluations,
averageTime: metrics.avgTime + 'ms'
});
Framework Agnostic
Whether you're using React, Vue, Express, Next.js, or vanilla JavaScript - Rule Engine JS just works:
// React
function useRuleValidation(rules, data) {
return useMemo(() =>
engine.evaluateExpr(rules, data), [rules, data]
);
}
// Express middleware
const createAccessMiddleware = (rule) => (req, res, next) => {
const result = engine.evaluateExpr(rule, req.user);
result.success ? next() : res.status(403).end();
};
// Vue computed property
computed: {
isEligible() {
return engine.evaluateExpr(this.businessRules, this.userData).success;
}
}
Getting Started (30 Seconds)
npm install rule-engine-js
import { createRuleEngine, createRuleHelpers } from 'rule-engine-js';
const engine = createRuleEngine();
const rules = createRuleHelpers();
// Your first rule
const canAccess = rules.and(
rules.gte('user.age', 18),
rules.eq('user.status', 'active')
);
// Test it
const result = engine.evaluateExpr(canAccess, {
user: { age: 25, status: 'active' }
});
console.log(result.success); // true
That's it. No configuration files, no complex setup - just clean, working code.
Why I Think You'll Love It
After using Rule Engine JS in production for several months, here's what I appreciate most:
- It stays out of your way - Simple API, predictable behavior
- Debugging is actually pleasant - Clear error messages and built-in logging
- Performance is transparent - See exactly how your rules are performing
- Security is handled - Protection against common attacks built-in
- It scales with your needs - From simple validation to complex business logic
What's Next?
I'm actively working on Rule Engine JS and would love your feedback. Whether you're dealing with complex business rules, form validation, or access control - give it a try and let me know what you think.
⭐ Star the repo: github.com/crafts69guy/rule-engine-js
📦 Try it now: npm install rule-engine-js
📚 Full docs: Comprehensive guides and examples included
🐛 Issues: Found a bug or have a feature request? I'd love to hear from you!
Rule Engine JS is MIT licensed and has zero dependencies. It works in Node.js 16+ and all modern browsers. Built by developers, for developers who are tired of hardcoding business logic.
Top comments (5)
It looks great! At my work, I do something similar, but in your code I found many interesting ideas, that I hadn't thought about. And now I can’t wait for Monday to try to implement them 😅
I have a question for you as a specialist who wrote the implementation of this rule system much better than me ☺️. If you don't mind, of course.
On top of rules executor, I have created event system that runs predefined action when a rule is “triggered”. By “triggered”, I mean when the rule's result switches from false to true.
In a system like that, users want access to a few specific operators: “changed”, “changed by”, and “changed from”. There are two problems with these.
The first one is that we need to pass the previous state of the context to the evaluate function. Without that, we would need to parse and modify each rule manually before evaluating them.
The second one is that we need to know if the “changed” operator returns “true”, because, unlike other operators, it must trigger on every run where the value is different from before.
What do you think about these cases?
So, I'm late, but I'm excited to tell you that the architecture I've built actually addresses both of those challenges head-on! 😊
Looking at your requirements, I can see you're dealing with the exact same problems I faced when building this system. Here's how I solved them:
Problem 1: Passing Previous State ✅ Solved
I created a StatefulRuleEngine wrapper that automatically manages state transitions. Instead of manually modifying rules, it enriches the context:
Problem 2: Change Operators & Triggering Logic ✅ Solved
I implemented all the change operators you mentioned in
src/operators/state.js
The brilliant part is the automatic triggering logic. The system detects when change operators are used and adjusts the triggering behavior:
Real Example
Please check it out beta version here
Is this a match with your case?
And again, your solution looks better 😁 Maybe instead of rewriting my code, I’ll try using your library 👍
Thanks a ton 🤠
I just whipped up this idea with huge help from
Claude Code
. That thing's crazy powerful - you gotta try it! Btw, the implementation above is just a beta 🤓. Let's test it out and hit me with your feedback!Thanks so much for the kind words! 😊 I'm glad you found some useful ideas in your implementation!
Your question about state change operators is great - this is actually an interesting architectural challenge that I've been thinking about as well. You've pinpointed two pain points:
Accessing previous state - Operators need to compare the current value to the previous value, meaning we need to pass the previous context through the evaluation pipeline somehow.
Different trigger semantics - Unlike regular operators that trigger on the transition from false to true, "changed" operators need to be triggered on every change.
I think the current architecture of the Rule Engine can handle this well, and I can give you a solution tonight, so stay tuned 😎