Fastify

rbacPlugin, registered once, declared per-route via config: { rbac: { resource, action } } — import { rbacPlugin } from 'rbac-fs/fastify'.

Usage

import Fastify from 'fastify';
import { RBAC } from 'rbac-fs';
import { rbacPlugin } from 'rbac-fs/fastify';

const rbac = new RBAC({ tenantId: 'acme-corp' });
await rbac.createRole('manager', { permissions: [{ resource: 'invoice', actions: ['approve'] }] });

const app = Fastify();

app.addHook('onRequest', async (request) => {
  const role = request.headers['x-user-role'];
  if (role) request.user = { id: 'demo-user', role };
});

await app.register(rbacPlugin, { rbac });

app.post(
  '/invoices/:id/approve',
  { config: { rbac: { resource: 'invoice', action: 'approve' } } },
  async (request) => ({ approved: request.params.id }),
);
Register rbacPlugin at the root app, not inside an encapsulated sub-plugin. Fastify's plugin encapsulation means a hook registered inside a child context never reaches sibling routes registered elsewhere — rbacPlugin is wrapped in fastify-plugin specifically to opt out of that and apply app-wide, the same approach @fastify/jwt uses internally.

Full runnable version: examples/09-fastify-plugin.mjs.