Scalable Authz
RBAC as a clean layer, not scattered if-statements
A role-based access control engine built to prove permissions can be a domain-agnostic layer — typed policies, composable roles, and audit-friendly checks — exercised across Posts and Events domains on Next.js App Router and MongoDB.
- Next.js App Router
- TypeScript
- MongoDB
- RBAC

01 / context
Authorization is where most apps quietly rot: role checks smeared across components, routes and API handlers, each slightly different, none testable. I wanted to build the version I'd want to inherit — permission logic as an explicit, typed layer that reads like policy, not archaeology.
02 / problem
The system had to answer three questions uniformly — who can do what to which resource — support role inheritance and resource-scoped rules, and stay framework-agnostic enough to drop into a Next.js app router project without contortions.
03 / decisions
Policies as data, checked in one place
const policy = definePolicy({
role: "editor",
can: ["post:read", "post:update"],
inherits: ["viewer"],
scope: { "post:update": { ownerOnly: true } },
});Roles compose through inheritance; resource-scoped conditions (like owner-only writes) are part of the policy, not a special case at the call site.
Two demo domains, one engine
Posts and Events exercise the engine with different shapes — ownership rules on one, RSVP capacity rules on the other — proving the layer doesn't leak domain assumptions.
04 / outcome
- Single audit surface: every permission decision traces to one policy object.
- Role inheritance and per-resource scoping without framework coupling.
- The patterns now ship in production work at Revnix — RBAC on real storefronts.
05 / what i'd do differently
Next step would be derived caching of resolved permissions per session, and a policy diff tool — the moment permissions multiply, seeing what changed between two policy versions becomes the real product.