Skip to content

Role-based Access Control

Problem

Implement role-based access control in a production-friendly way with @vielzeug/routeit while keeping setup and cleanup explicit.

Runnable Example

The snippet below is copy-paste runnable in a TypeScript project with @vielzeug/routeit installed.

Using @vielzeug/permit for fine-grained permissions:

ts
import { createRouter } from '@vielzeug/routeit';
import { createPermit } from '@vielzeug/permit';
import type { Middleware } from '@vielzeug/routeit';
import type { BaseUser, PermissionAction } from '@vielzeug/permit';

const permit = createPermit();

permit.set('admin', 'posts', { read: true, create: true, update: true, delete: true });
permit.set('editor', 'posts', {
  read: true,
  create: true,
  update: (user, data) => user.id === data.authorId,
  delete: false,
});

const router = createRouter();

const requireAuth: Middleware = async (ctx, next) => {
  const user = await getCurrentUser();
  if (!user) {
    await ctx.navigate('/login');
    return;
  }
  ctx.locals.user = user;
  await next();
};

const requirePermission =
  (resource: string, action: PermissionAction): Middleware =>
  async (ctx, next) => {
    const user = ctx.locals.user as BaseUser;
    if (!permit.check(user, resource, action)) {
      await ctx.navigate('/forbidden');
      return;
    }
    await next();
  };

router
  .on('/posts', renderPosts, { middleware: [requireAuth, requirePermission('posts', 'read')] })
  .on('/posts/new', renderNewPost, { middleware: [requireAuth, requirePermission('posts', 'create')] })
  .on('/posts/:id/edit', ({ params }) => renderEditPost(params.id), {
    middleware: [requireAuth, requirePermission('posts', 'update')],
  })
  .start();

Expected Output

  • The example runs without type errors in a standard TypeScript setup.
  • The main flow produces the behavior described in the recipe title.

Common Pitfalls

  • Forgetting cleanup/dispose calls can leak listeners or stale state.
  • Skipping explicit typing can hide integration issues until runtime.
  • Not handling error branches makes examples harder to adapt safely.