Rule Cascade
Get started

Your first rule in an app

A step-by-step walkthrough for beginners - write one business rule, test it, and enforce it in an Express, FastAPI, Spring Boot or Go service with one line, then change the rule without touching the code.

This page takes you from nothing to an API that refuses a bad order, in about fifteen minutes. You need a terminal and one of Node.js 20+, Python 3.10+, Java 17+ or Go 1.22+. No Rule Cascade knowledge is assumed.

The idea in one sentence: you write the business rule once, as data (YAML), and every part of your system asks the same rule instead of re-implementing it in code. Here the rule is "an order has at most 10 items".

Install the rcas command

curl -fsSL https://rulescascade.com/install.sh | sh        # macOS and Linux
irm https://rulescascade.com/install.ps1 | iex             # Windows PowerShell
npx -y @rules-cascade/cli version                          # or run it through npm, nothing to install

rcas checks, tests and compiles rules. Your application does not need it at run time.

Create a project with a first rule

mkdir shop && cd shop
rcas init --name shop
rcas init /home/you/shop (shop, ids shop.*)
  created      rcas.yaml
  created      rules/example.ruleset.yaml
  created      rules/order.schema.json
  created      rules/LOADING.md

Open rules/example.ruleset.yaml. The part that matters (shortened):

rules/example.ruleset.yaml
params:
  maxQuantity: { type: integer, default: 10 }   # the limit, as a parameter

rules:
  - id: order.quantity.max                       # a stable name for the rule
    target: { entity: Order, field: /quantity }
    operations: [create, update]
    when:   { op: exists, args: [{ var: data.quantity }] }                      # only when there is a quantity
    assert: { op: lte, args: [{ var: data.quantity }, { var: params.maxQuantity }] }   # quantity <= 10
    severity: error                              # error blocks; warning only warns
    finding: { code: EXAMPLE-ORD-001, message: order.quantityTooHigh, args: { max: { var: params.maxQuantity } } }

messages:
  en:
    order.quantityTooHigh: "You can order at most {max} items."

Read it as: when an Order is created or updated and it has a quantity, the quantity must be at most maxQuantity; otherwise block it with code EXAMPLE-ORD-001. The file also has a second rule (a warning for large orders without a note) and golden tests at the end: example orders with the answer the rules must give.

Check the rules

rcas check
shop.example@0.1.0  sha256:21eff22a5c25...  2 rules (2 client-safe), 1 params
  3 golden tests, 0 failed

check validates the file and runs the golden tests. Change default: 10 to default: 2 and run it again: the test "three items are allowed" fails. That is the point of golden tests: a rule change that breaks an agreed example never goes unnoticed. Put it back to 10.

Compile

rcas compile --all
shop.example@0.1.0  sha256:21eff22a...  -> shop.example.bundle.json, shop.example.client.manifest.json

build/rules/shop.example.bundle.json is what your application loads: the rules, checked and compiled, with a checksum. You can try it without any application:

echo '{"entity":"Order","operation":"create","data":{"quantity":11}}' | rcas evaluate --bundle build/rules/shop.example.bundle.json -

The answer has "decision": "deny" and one finding, EXAMPLE-ORD-001, "You can order at most 10 items."

Enforce the rule in your application

Version

enforce, the middleware, the decorator and the 422 helpers are in the packages from 1.0.0-alpha.7.

Pick your stack. In each one the rules are loaded once, at start-up, and one line (a middleware, a decorator, an annotation) enforces them on the endpoint: a denied order never reaches your code, and the caller gets a 422 answer that says why.

npm install express @rules-cascade/core
server.mjs
import { readFileSync } from 'node:fs';
import express from 'express';
import { RuleSet } from '@rules-cascade/core';
import { rulesMiddleware, rulesErrorHandler } from '@rules-cascade/core/http';

// Once, at start-up: a bundle that cannot be read stops the server.
const rules = RuleSet.fromBundle(JSON.parse(readFileSync('build/rules/shop.example.bundle.json', 'utf8')));

const app = express();
app.use(express.json());

app.post('/orders', rulesMiddleware(rules, { entity: 'Order', operation: 'create' }), (req, res) => {
  // Only allowed orders get here. Warnings (non-blocking findings) come with the evaluation.
  const warnings = res.locals.ruleEvaluation.findings.map((f) => f.message);
  res.status(201).json({ order: req.body, warnings });
});

app.use(rulesErrorHandler()); // a RuleViolationError thrown anywhere becomes a 422 answer

app.listen(3000);

Fastify: { preHandler: rulesPreHandler(rules, { entity: 'Order', operation: 'create' }) } on the route. Anywhere else (a NestJS service, a Next.js route handler, a queue consumer): rules.enforce(request) returns the result or throws RuleViolationError, whose problem() is the 422 body.

Try it

curl -i -X POST localhost:3000/orders -H 'content-type: application/json' -d '{"quantity": 3}'
curl -i -X POST localhost:3000/orders -H 'content-type: application/json' -d '{"quantity": 6}'
curl -i -X POST localhost:3000/orders -H 'content-type: application/json' -d '{"quantity": 11, "note": "rush"}'
OrderAnswerWhy
3 items201allowed
6 items201, with the warning "Add a note to an order of five items or more."a warning does not block
11 items422 application/problem+jsonorder.quantity.max blocks it

The 422 body is the same in every language:

{
  "type": "urn:rule-cascade:rule-violation",
  "title": "Business rule violation",
  "status": 422,
  "detail": "Denied by 1 blocking finding: EXAMPLE-ORD-001.",
  "evaluation": {
    "decision": "deny",
    "findings": [
      { "rule": "order.quantity.max", "code": "EXAMPLE-ORD-001", "message": "You can order at most 10 items.",
        "fields": ["/quantity"], "blocking": true, "severity": "error", "status": "open", "resolution": "none",
        "source": "shop.example@0.1.0" }
    ],
    "...": "ruleset, version, checksum, effects, commands"
  }
}

A front end shows message next to the input that fields names (/quantity).

Change the rule, not the code

The business raises the limit to 20. In rules/example.ruleset.yaml set default: 20, and update the golden test "eleven items are denied": 21 items, and the message "You can order at most 20 items." Then:

rcas check && rcas compile --all

Restart the application (or let it reload: every runtime has a holder that refreshes bundles on a schedule). Eleven items are now accepted. No application code changed, in any language.

What you did

  1. Wrote a rule as data, with a parameter for the limit and tests for the examples that matter.
  2. Checked and compiled it into a bundle with a checksum.
  3. Enforced it with one line on the endpoint; the rule's answer and its 422 response are identical in TypeScript, Python, Java and Go.

Next

  • The same rule in the browser, for instant feedback in a form: React form.
  • A real API: actor and roles, updates with the stored record, computed values and commands after saving: backend API.
  • Rules that already live in your code: existing project.
  • Let your AI coding agent do it: rcas agent install gives it the instructions and a skill for your stack: AI coding tools.

On this page