expressadapters

One middleware shape, three Node HTTP frameworks

rbac-fs ships three separate HTTP-framework adapters — Express, Koa, Fastify — and deliberately keeps them converged on the same call shape rather than three unrelated APIs.

The shared signature

rbacMiddleware(rbac, resource, action, options?)

Express and Koa both use this exact function. A denied check returns a plain 403 by default; both resource and action can also be derived per-request via a function instead of a static string, for routes like /invoices/:id/approve where the resource ID matters but the permission's resource name ('invoice') doesn't change per-request.

Express

import { rbacMiddleware } from 'rbac-fs/express';

app.post(
  '/invoices/:id/approve',
  rbacMiddleware(rbac, 'invoice', 'approve'),
  (req, res) => res.json({ approved: req.params.id }),
);

Reads the user from req.user — the convention practically every Express auth middleware (Passport included) already uses, so rbac-fs doesn't ask you to attach the user anywhere unusual.

Koa

import { rbacMiddleware } from 'rbac-fs/koa';

const approveInvoice = rbacMiddleware(rbac, 'invoice', 'approve');
app.use(async (ctx, next) => {
  if (ctx.method === 'POST' && /* route match */) {
    return approveInvoice(ctx, async () => { ctx.body = { approved: true }; });
  }
  return next();
});

Same function, but reads the user from ctx.state.user — Koa's own documented convention, not ctx.req. This is the one place the two adapters genuinely diverge, and it's Koa's convention forcing that, not an inconsistency in rbac-fs.

Fastify: a plugin, not a middleware function

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

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

app.post('/invoices/:id/approve',
  { config: { rbac: { resource: 'invoice', action: 'approve' } } },
  async (request) => ({ approved: request.params.id }),
);

Fastify's own plugin/hook architecture doesn't have an Express-style middleware chain, so the adapter is a registered onRequest-style plugin instead, and permission config moves into each route's config.rbac object rather than being a function wrapped around the handler. Same underlying rbac.can() call, framework-idiomatic shape on top.

Runnable walkthroughs for each: Express tutorial, plus Koa and Fastify companions in this series.