Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
How We Scaled from 10 to 750+ Permissions Without Writing a Single Middleware Function | Metaprogramming
Dec 15, 2025
44 views
Written by Prashant Basnet
👋 Welcome to my Signature, a space between logic and curiosity.
I’m a Software Development Engineer who loves turning ideas into systems that work beautifully.
This space captures the process: the bugs, breakthroughs, and “aha” moments that keep me building.
This thread is about auto Generating Semantic role based access control middleware using Metaprogramming.
When building a scalable API, Role-Based Access Control (RBAC) usually starts simple. You have a few permissions, a check function, and life is good.
// The "Good Old Days" app.get('/orders', { preHandler: [ensurePermission('orders:read')], handler: getOrders });But as an application grows, so does the complexity.
In a typical Enterprise SaaS platform, you might have dozens of modules (Inventory, Billing, CRM, User Management) and hundreds of distinct permissions.
Suddenly, simple string-based checks become a liability.
Magic String Hell:
Discovery Issues:
Refactoring Nightmares:
We needed a solution that gave us the flexibility of granular permissions with the safety and developer joy of modern TypeScript.
Here is how we auto-generated type-safe semantic middleware methods without writing a single line of boilerplate.
1. The Source of Truth: Structured Constants
Organize permissions into a structured, nested hierarchy.
Instead of a flat list, we define a master PERMISSIONS object that groups everything by domain.
We can use a helper to standardize CRUD actions
// permissions.ts export const PERMISSIONS = { // Inventory Module INVENTORY: { READ: 'inventory.items:read', CREATE: 'inventory.items:create', UPDATE: 'inventory.items:update', DELETE: 'inventory.items:delete', }, // Orders Module ORDERS: { READ: 'sales.orders:read', APPROVE: 'sales.orders:approve', CANCEL: 'sales.orders:cancel', } } as const;This gives us a Single Source of Truth, but we are still passing strings to our middleware.
2. The Generator: Turning Strings into Methods
We want our API routes to read like English sentences. Instead of ensurePermission('sales.orders:approve'), we want ensureCanApproveOrders.
Writing hundreds of these wrapper functions manually is tedious and error-prone.
Instead, we can write a Semantic Method Generator.
The logic is simple:
Here is the simplified concept:
// method.generator.ts function generateSemanticMethods(permissions) { const methods = {};// Walk through every permission group Object.values(permissions).forEach((group) => { Object.entries(group).forEach(([action, permissionString])const methodName = `ensureCan${capitalize(action)}${capitalize(resourceName)}`;methods[methodName] = (request, reply) => { return ensureExactPermission(permissionString)(request,reply); }; return methods; }The Result: A Developer Experience Upgrade
Before
app.post('/orders/:id/approve', { preHandler: [ ensureAuthentication, // Hope you didn't make a typo! // And is it 'orders:approve' or 'sales.orders:approve'? ensureExactPermission('sales.orders:approve') ], handler: approveOrder });After
Now, we have full Intellisense support. A developer types app.ensureCan and their IDE immediately lists every valid action in the system.
app.post('/orders/:id/approve', { preHandler: [ app.ensureAuthentication, // Full autocomplete + Type safety + Descriptive names app.ensureCanApproveOrders ], handler: approveOrder });Why This Matters
As we add new modules (like Billing or Analytics), the corresponding middleware methods appear automatically.
Metaprogramming is a tool that should be used sparingly, but when applied to infrastructure code.
Fifty lines of generation code replaced thousands of lines of boilerplate and eliminated an entire class of production bugs.
#TypeScript #Nodejs #Fastify #Engineering #DesignPatterns