npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@prodforcode/rbac-nestjs

v1.1.0

Published

NestJS integration for @prodforcode/rbac-core with decorators, guards, and dynamic modules

Readme

@prodforcode/rbac-nestjs

NestJS integration for @prodforcode/rbac-core with decorators, guards, interceptors, and dynamic module configuration.

Features

  • Dynamic Module - Configure RBAC with forRoot and forRootAsync
  • Decorators - @Roles(), @Permissions(), @CurrentUser(), and more
  • Guards - RolesGuard and PermissionsGuard for route protection
  • Interceptors - AuditInterceptor for automatic audit logging
  • Dependency Injection - Full DI support for adapters and services
  • TypeScript - Complete type safety
  • Testing - Easy to mock and test

Installation

npm install @prodforcode/rbac-nestjs @prodforcode/rbac-core @nestjs/common @nestjs/core reflect-metadata rxjs
# or
yarn add @prodforcode/rbac-nestjs @prodforcode/rbac-core @nestjs/common @nestjs/core reflect-metadata rxjs
# or
pnpm add @prodforcode/rbac-nestjs @prodforcode/rbac-core @nestjs/common @nestjs/core reflect-metadata rxjs

Quick Start

import { Module } from '@nestjs/common';
import { RbacModule } from '@prodforcode/rbac-nestjs';
import { TypeORMAdapter } from '@prodforcode/rbac-typeorm';
import { RedisCacheAdapter } from '@prodforcode/rbac-cache';

@Module({
  imports: [
    RbacModule.forRoot({
      adapter: new TypeORMAdapter(dataSource),
      cache: new RedisCacheAdapter({ redis }),
      options: {
        enableAuditLog: true,
        cacheTtl: 300
      }
    })
  ]
})
export class AppModule {}

Module Configuration

Synchronous Configuration

Use forRoot() when your configuration is static:

import { Module } from '@nestjs/common';
import { RbacModule } from '@prodforcode/rbac-nestjs';
import { TypeORMAdapter } from '@prodforcode/rbac-typeorm';
import { MemoryCacheAdapter } from '@prodforcode/rbac-cache';

@Module({
  imports: [
    RbacModule.forRoot({
      // Required: Database adapter
      adapter: new TypeORMAdapter(dataSource),

      // Optional: Cache adapter (defaults to in-memory)
      cache: new MemoryCacheAdapter({
        maxSize: 10000,
        defaultTtl: 300
      }),

      // Optional: Audit logger
      auditLogger: new DatabaseAuditLogger(db),

      // Optional: Configuration options
      options: {
        enableAuditLog: true,
        cacheTtl: 300,
        defaultRole: 'viewer'
      }
    })
  ]
})
export class AppModule {}

Asynchronous Configuration

Use forRootAsync() when you need to inject dependencies:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RbacModule } from '@prodforcode/rbac-nestjs';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
        database: config.get('DB_NAME'),
        entities: [/* ... */]
      }),
      inject: [ConfigService]
    }),
    RbacModule.forRootAsync({
      imports: [ConfigModule, TypeOrmModule],
      useFactory: (config: ConfigService, dataSource: DataSource) => ({
        adapter: new TypeORMAdapter(dataSource),
        options: {
          enableAuditLog: config.get('ENABLE_AUDIT'),
          cacheTtl: config.get('CACHE_TTL', 300)
        }
      }),
      inject: [ConfigService, DataSource]
    })
  ]
})
export class AppModule {}

Decorators

@Roles()

Specify required roles for a route. User must have at least ONE of the specified roles.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles, RolesGuard } from '@prodforcode/rbac-nestjs';

@Controller('admin')
@UseGuards(RolesGuard)
export class AdminController {
  @Get('dashboard')
  @Roles('admin', 'superAdmin')
  getDashboard() {
    return { message: 'Admin dashboard' };
  }

  @Get('settings')
  @Roles('superAdmin')
  getSettings() {
    return { message: 'System settings' };
  }
}

@Permissions()

Specify required permissions for a route. User must have ALL specified permissions.

import { Controller, Post, UseGuards } from '@nestjs/common';
import { Permissions, PermissionsGuard } from '@prodforcode/rbac-nestjs';

@Controller('users')
@UseGuards(PermissionsGuard)
export class UsersController {
  @Post()
  @Permissions('users:create')
  createUser() {
    return { message: 'User created' };
  }

  @Post(':id/ban')
  @Permissions('users:update', 'users:ban')
  banUser() {
    return { message: 'User banned' };
  }
}

@RequiresRole()

Alternative decorator that throws an exception if role is missing:

import { Controller, Get } from '@nestjs/common';
import { RequiresRole } from '@prodforcode/rbac-nestjs';

@Controller('reports')
export class ReportsController {
  @Get('financial')
  @RequiresRole('accountant')
  getFinancialReport() {
    return { message: 'Financial report' };
  }
}

@RequiresPermission()

Alternative decorator that throws if permission is missing:

import { Controller, Delete } from '@nestjs/common';
import { RequiresPermission } from '@prodforcode/rbac-nestjs';

@Controller('posts')
export class PostsController {
  @Delete(':id')
  @RequiresPermission('posts:delete')
  deletePost() {
    return { message: 'Post deleted' };
  }
}

@RequiresAny()

User must have at least one of the specified permissions:

import { Controller, Get } from '@nestjs/common';
import { RequiresAny } from '@prodforcode/rbac-nestjs';

@Controller('content')
export class ContentController {
  @Get()
  @RequiresAny('posts:read', 'articles:read', 'pages:read')
  getContent() {
    return { message: 'Content list' };
  }
}

@RequiresAll()

User must have all specified permissions:

import { Controller, Post } from '@nestjs/common';
import { RequiresAll } from '@prodforcode/rbac-nestjs';

@Controller('deployments')
export class DeploymentsController {
  @Post()
  @RequiresAll('deployments:create', 'infrastructure:write', 'secrets:read')
  createDeployment() {
    return { message: 'Deployment created' };
  }
}

@CurrentUser()

Extract the current user from the request:

import { Controller, Get } from '@nestjs/common';
import { CurrentUser } from '@prodforcode/rbac-nestjs';

interface User {
  id: string;
  email: string;
}

@Controller('profile')
export class ProfileController {
  @Get()
  getProfile(@CurrentUser() user: User) {
    return { userId: user.id, email: user.email };
  }
}

@Public()

Mark a route as public (skip all RBAC checks):

import { Controller, Get } from '@nestjs/common';
import { Public } from '@prodforcode/rbac-nestjs';

@Controller('health')
export class HealthController {
  @Get()
  @Public()
  check() {
    return { status: 'ok' };
  }
}

Guards

RolesGuard

Validates that the user has the required roles.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard, Roles } from '@prodforcode/rbac-nestjs';

@Controller('api')
@UseGuards(RolesGuard)
export class ApiController {
  @Get('data')
  @Roles('admin')
  getData() {
    return { data: [] };
  }
}

PermissionsGuard

Validates that the user has the required permissions.

import { Controller, Post, UseGuards } from '@nestjs/common';
import { PermissionsGuard, Permissions } from '@prodforcode/rbac-nestjs';

@Controller('api')
@UseGuards(PermissionsGuard)
export class ApiController {
  @Post('data')
  @Permissions('data:create')
  createData() {
    return { success: true };
  }
}

Global Guards

Apply guards globally to all routes:

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { RbacModule, PermissionsGuard } from '@prodforcode/rbac-nestjs';

@Module({
  imports: [RbacModule.forRoot({ /* ... */ })],
  providers: [
    {
      provide: APP_GUARD,
      useClass: PermissionsGuard
    }
  ]
})
export class AppModule {}

Combining Guards

Use multiple guards together:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard, Roles } from '@prodforcode/rbac-nestjs';

@Controller('secure')
@UseGuards(AuthGuard('jwt'), RolesGuard)
export class SecureController {
  @Get('data')
  @Roles('admin')
  getData() {
    return { data: [] };
  }
}

Interceptors

AuditInterceptor

Automatically log all authorization events:

import { Controller, UseInterceptors } from '@nestjs/common';
import { AuditInterceptor } from '@prodforcode/rbac-nestjs';

@Controller('users')
@UseInterceptors(AuditInterceptor)
export class UsersController {
  // All requests to this controller will be audited
}

Global Audit Interceptor

Apply audit logging to all routes:

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { RbacModule, AuditInterceptor } from '@prodforcode/rbac-nestjs';

@Module({
  imports: [RbacModule.forRoot({ /* ... */ })],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: AuditInterceptor
    }
  ]
})
export class AppModule {}

Injecting RBACEngine

Access the RBAC engine directly in your services:

import { Injectable } from '@nestjs/common';
import { RBACEngine } from '@prodforcode/rbac-core';

@Injectable()
export class UsersService {
  constructor(private readonly rbac: RBACEngine) {}

  async assignEditorRole(userId: string) {
    const editorRole = await this.rbac.getRoleByName('editor');
    if (!editorRole) {
      throw new Error('Editor role not found');
    }

    await this.rbac.assignRole({
      userId,
      roleId: editorRole.id
    });
  }

  async checkPermission(userId: string, permission: string) {
    return await this.rbac.can(userId, permission);
  }

  async getUserPermissions(userId: string) {
    return await this.rbac.getUserPermissions(userId);
  }
}

Complete Example

Setup

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RbacModule } from '@prodforcode/rbac-nestjs';
import { TypeORMAdapter, entities } from '@prodforcode/rbac-typeorm';
import { RedisCacheAdapter } from '@prodforcode/rbac-cache';
import { DataSource } from 'typeorm';
import Redis from 'ioredis';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'admin',
      password: 'password',
      database: 'myapp',
      entities: entities,
      synchronize: true
    }),
    RbacModule.forRootAsync({
      imports: [TypeOrmModule],
      useFactory: (dataSource: DataSource) => {
        const redis = new Redis({
          host: 'localhost',
          port: 6379
        });

        return {
          adapter: new TypeORMAdapter(dataSource),
          cache: new RedisCacheAdapter({ redis }),
          options: {
            enableAuditLog: true,
            cacheTtl: 300
          }
        };
      },
      inject: [DataSource]
    })
  ]
})
export class AppModule {}

Controller

// users.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  UseGuards,
  UseInterceptors,
  Param,
  Body
} from '@nestjs/common';
import {
  Roles,
  Permissions,
  CurrentUser,
  RolesGuard,
  PermissionsGuard,
  AuditInterceptor
} from '@prodforcode/rbac-nestjs';
import { AuthGuard } from '@nestjs/passport';

interface User {
  id: string;
  email: string;
}

@Controller('users')
@UseGuards(AuthGuard('jwt'), PermissionsGuard)
@UseInterceptors(AuditInterceptor)
export class UsersController {
  @Get()
  @Permissions('users:read')
  async listUsers() {
    return [{ id: '1', email: '[email protected]' }];
  }

  @Get('me')
  @Permissions('users:read:own')
  async getProfile(@CurrentUser() user: User) {
    return { id: user.id, email: user.email };
  }

  @Post()
  @Permissions('users:create')
  async createUser(@Body() data: any) {
    return { id: 'new-user', ...data };
  }

  @Put(':id')
  @Permissions('users:update')
  async updateUser(@Param('id') id: string, @Body() data: any) {
    return { id, ...data };
  }

  @Delete(':id')
  @Permissions('users:delete')
  async deleteUser(@Param('id') id: string) {
    return { deleted: true };
  }

  @Post(':id/ban')
  @Roles('admin', 'moderator')
  @Permissions('users:ban')
  async banUser(@Param('id') id: string) {
    return { banned: true };
  }
}

Service

// rbac.service.ts
import { Injectable } from '@nestjs/common';
import { RBACEngine } from '@prodforcode/rbac-core';

@Injectable()
export class RbacService {
  constructor(private readonly rbac: RBACEngine) {}

  async setupDefaultRoles() {
    // Create viewer role
    const viewer = await this.rbac.createRole({
      name: 'viewer',
      permissions: ['posts:read', 'comments:read']
    });

    // Create editor role
    const editor = await this.rbac.createRole({
      name: 'editor',
      permissions: ['posts:*', 'comments:*'],
      parentRoles: [viewer.id]
    });

    // Create admin role
    await this.rbac.createRole({
      name: 'admin',
      permissions: ['**'],
      parentRoles: [editor.id]
    });
  }

  async assignDefaultRole(userId: string) {
    const viewer = await this.rbac.getRoleByName('viewer');
    if (viewer) {
      await this.rbac.assignRole({
        userId,
        roleId: viewer.id
      });
    }
  }

  async promoteToEditor(userId: string) {
    const editor = await this.rbac.getRoleByName('editor');
    if (editor) {
      await this.rbac.assignRole({
        userId,
        roleId: editor.id
      });
    }
  }

  async getUserPermissions(userId: string) {
    return await this.rbac.getUserPermissions(userId);
  }

  async can(userId: string, permission: string) {
    return await this.rbac.can(userId, permission);
  }
}

Authentication Integration

JWT Strategy

// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { RBACEngine } from '@prodforcode/rbac-core';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly rbac: RBACEngine) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'your-secret-key'
    });
  }

  async validate(payload: any) {
    // Attach user permissions to request
    const permissions = await this.rbac.getUserPermissions(payload.sub);

    return {
      id: payload.sub,
      email: payload.email,
      permissions: permissions.map(p =>
        `${p.resource}:${p.action}${p.scope ? ':' + p.scope : ''}`
      )
    };
  }
}

Testing

Unit Testing

import { Test, TestingModule } from '@nestjs/testing';
import { RBACEngine } from '@prodforcode/rbac-core';
import { UsersController } from './users.controller';

describe('UsersController', () => {
  let controller: UsersController;
  let rbac: RBACEngine;

  beforeEach(async () => {
    const mockRbac = {
      can: jest.fn(),
      getUserPermissions: jest.fn()
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        {
          provide: RBACEngine,
          useValue: mockRbac
        }
      ]
    }).compile();

    controller = module.get<UsersController>(UsersController);
    rbac = module.get<RBACEngine>(RBACEngine);
  });

  it('should allow user with permission', async () => {
    jest.spyOn(rbac, 'can').mockResolvedValue(true);
    const result = await controller.listUsers();
    expect(result).toBeDefined();
  });
});

E2E Testing

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';

describe('RBAC (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule]
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/users (GET) - should require authentication', () => {
    return request(app.getHttpServer())
      .get('/users')
      .expect(401);
  });

  it('/users (GET) - should allow with valid token', () => {
    return request(app.getHttpServer())
      .get('/users')
      .set('Authorization', 'Bearer valid-token')
      .expect(200);
  });

  afterAll(async () => {
    await app.close();
  });
});

API Reference

RbacModule

class RbacModule {
  static forRoot(options: RbacModuleOptions): DynamicModule;
  static forRootAsync(options: RbacModuleAsyncOptions): DynamicModule;
}

interface RbacModuleOptions {
  adapter: Type<IRBACAdapter> | IRBACAdapter;
  auditLogger?: Type<IAuditLogger> | IAuditLogger;
  cache?: Type<IRBACCache> | IRBACCache;
  options?: {
    enableAuditLog?: boolean;
    cacheTtl?: number;
    defaultRole?: string;
  };
}

Decorators

// Role-based
@Roles(...roles: string[])
@RequiresRole(role: string)

// Permission-based
@Permissions(...permissions: string[])
@RequiresPermission(permission: string)
@RequiresAny(...permissions: string[])
@RequiresAll(...permissions: string[])

// Utility
@CurrentUser()
@Public()

Guards

class RolesGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean>;
}

class PermissionsGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean>;
}

Interceptors

class AuditInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
}

Best Practices

  1. Use Guards Consistently - Apply guards at the controller or global level
  2. Prefer Permissions over Roles - More granular control
  3. Cache Aggressively - Use Redis for multi-instance deployments
  4. Audit Important Actions - Enable audit logging for compliance
  5. Test Authorization - Write tests for permission checks
  6. Handle Errors Gracefully - Provide user-friendly error messages

License

MIT