Getting started
Rungs is a library: your application calls it to send a record (an expense, a leave request, a purchase order) through approval steps, and Rungs decides who must approve, keeps the state and writes the audit trail. It runs in your Node.js process, on your database.
Install
Section titled “Install”npm install @rungsjs/core @rungsjs/store-postgresRungs needs Node.js 22 or newer and is published as ES modules with TypeScript types.
@rungsjs/store-postgres keeps everything in PostgreSQL 15 or newer; for tests and prototypes,
@rungsjs/store-memory keeps it in memory.
Your first flow
Section titled “Your first flow”An expense goes to the requester’s manager, then to finance when it is above 1,000:
import { createRungs } from "@rungsjs/core";import { memoryStore } from "@rungsjs/store-memory";
const managers: Record<string, string> = { u_alice: "u_bob" };
const rungs = createRungs({ store: memoryStore(), clock: () => new Date(), ids: () => crypto.randomUUID(), roles: async (role) => (role === "finance" ? ["u_fin1", "u_fin2"] : []), resolvers: { directManager: async ({ request }) => { const manager = managers[request.requesterId]; return manager ? [manager] : []; }, },});
await rungs.flows.register({ key: "expense", steps: [ { id: "manager", approvers: { type: "resolver", name: "directManager" } }, { id: "finance", approvers: { type: "role", role: "finance" }, // either finance user may decide when: { field: "amount", op: "gt", value: 1000 }, deadline: "P2D", }, ],});
const request = await rungs.submit({ flow: "expense", requesterId: "u_alice", subject: { type: "expense", id: "EXP-1042" }, payload: { amount: 2500 },});
await rungs.approve({ requestId: request.id, actorId: "u_bob", comment: "Looks fine" });const done = await rungs.approve({ requestId: request.id, actorId: "u_fin2" });
console.log(done.status); // "approved"Runs in CI:examples/docs-snippets/src/quick-start.ts
What happened:
createRungsgets a store and the things Rungs never decides by itself: the time, how to make IDs, and how to find approvers.rolesturns a role into users;directManageris a resolver you write, here from a lookup table.flows.registerstores a versioned flow. Each request keeps the version it started with.submitstarts a request. Both steps apply because 2,500 is above 1,000, so Bob gets a task first.approverecords each decision. Either finance user may decide, so Bob’s and one finance approval complete the request.
Next steps
Section titled “Next steps”ConceptsFlows, approvers, conditions, delegation, deadlines and the audit trail.
Edge casesWhat Rungs does when real organizations get in the way.
Example appNext.js with an inbox, timeline, delegation and admin tools. Run it with pnpm demo.
PlaygroundWrite a scenario, see every step, approver and decision as a timeline.