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

@nestbolt/permissions

v0.1.0

Published

Roles & permissions (RBAC) for NestJS applications with TypeORM support.

Readme

This package provides a role-based access control (RBAC) system for NestJS that lets you manage roles and permissions with TypeORM, protect routes with guards, and check permissions programmatically.

Once installed, using it is as simple as:

@UseGuards(JwtAuthGuard, RolesGuard)
@RequireRoles('admin')
@Get('admin/dashboard')
dashboard() { return { message: 'Welcome, admin!' }; }

Table of Contents

Installation

Install the package via npm:

npm install @nestbolt/permissions

Or via yarn:

yarn add @nestbolt/permissions

Or via pnpm:

pnpm add @nestbolt/permissions

Peer Dependencies

This package requires the following peer dependencies, which you likely already have in a NestJS project:

@nestjs/common    ^10.0.0 || ^11.0.0
@nestjs/core      ^10.0.0 || ^11.0.0
@nestjs/typeorm   ^10.0.0 || ^11.0.0
typeorm           ^0.3.0
class-validator   ^0.14.0
class-transformer ^0.5.0
reflect-metadata  ^0.1.13 || ^0.2.0

Optional:

@nestjs/event-emitter ^2.0.0 || ^3.0.0   # For permission/role lifecycle events

Quick Start

1. Implement the PermissionUserRepository

Create a repository that bridges your User entity with the permission system:

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import {
  PermissionUserRepository,
  UserHasRolesEntity,
  UserHasPermissionsEntity,
} from "@nestbolt/permissions";

@Injectable()
export class TypeOrmPermissionUserRepository implements PermissionUserRepository {
  constructor(
    @InjectRepository(UserHasRolesEntity)
    private readonly userRolesRepo: Repository<UserHasRolesEntity>,
    @InjectRepository(UserHasPermissionsEntity)
    private readonly userPermissionsRepo: Repository<UserHasPermissionsEntity>,
  ) {}

  async getRoleIds(userId: string): Promise<string[]> {
    const rows = await this.userRolesRepo.find({ where: { userId } });
    return rows.map((r) => r.roleId);
  }

  async getDirectPermissionIds(userId: string): Promise<string[]> {
    const rows = await this.userPermissionsRepo.find({ where: { userId } });
    return rows.map((r) => r.permissionId);
  }

  async attachRoles(userId: string, roleIds: string[]): Promise<void> {
    const entities = roleIds.map((roleId) =>
      this.userRolesRepo.create({ userId, roleId }),
    );
    await this.userRolesRepo.save(entities);
  }

  async detachRoles(userId: string, roleIds: string[]): Promise<void> {
    for (const roleId of roleIds) {
      await this.userRolesRepo.delete({ userId, roleId });
    }
  }

  async detachAllRoles(userId: string): Promise<void> {
    await this.userRolesRepo.delete({ userId });
  }

  async attachPermissions(
    userId: string,
    permissionIds: string[],
  ): Promise<void> {
    const entities = permissionIds.map((permissionId) =>
      this.userPermissionsRepo.create({ userId, permissionId }),
    );
    await this.userPermissionsRepo.save(entities);
  }

  async detachPermissions(
    userId: string,
    permissionIds: string[],
  ): Promise<void> {
    for (const permissionId of permissionIds) {
      await this.userPermissionsRepo.delete({ userId, permissionId });
    }
  }

  async detachAllPermissions(userId: string): Promise<void> {
    await this.userPermissionsRepo.delete({ userId });
  }

  async userExists(userId: string): Promise<boolean> {
    // Implement based on your User entity
    return true;
  }
}

2. Register the module in AppModule

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { PermissionsModule } from "@nestbolt/permissions";
import { TypeOrmPermissionUserRepository } from "./repositories/permission-user.repository";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      /* your config */
    }),
    PermissionsModule.forRoot({
      userRepository: TypeOrmPermissionUserRepository,
    }),
  ],
})
export class AppModule {}

3. Use guards and decorators on your routes

import { Controller, Get, UseGuards } from "@nestjs/common";
import { RequireRoles, RolesGuard } from "@nestbolt/permissions";
import { JwtAuthGuard } from "@nestbolt/authentication";

@Controller("admin")
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @RequireRoles("admin")
  @Get("dashboard")
  dashboard() {
    return { message: "Welcome, admin!" };
  }
}

Module Configuration

Static Configuration (forRoot)

PermissionsModule.forRoot({
  userRepository: TypeOrmPermissionUserRepository,
  defaultGuardName: "default",
  enableWildcardPermissions: false,
  cache: { enabled: true, ttl: 86400000 },
  displayPermissionInException: false,
  displayRoleInException: false,
});

Async Configuration (forRootAsync)

PermissionsModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    userRepository: TypeOrmPermissionUserRepository,
    enableWildcardPermissions: config.get("ENABLE_WILDCARD_PERMISSIONS", false),
  }),
});

The module is registered as global — you only need to import it once.

Using the Guards & Decorators

Require specific permissions

@UseGuards(JwtAuthGuard, PermissionsGuard)
@RequirePermissions('posts.create', 'posts.edit')
@Post('posts')
createPost() { /* user must have ALL listed permissions */ }

Require specific roles

@UseGuards(JwtAuthGuard, RolesGuard)
@RequireRoles('admin', 'editor')
@Get('manage')
manage() { /* user must have ANY of the listed roles */ }

Require roles OR permissions

@UseGuards(JwtAuthGuard, RolesOrPermissionsGuard)
@RequireRolesOrPermissions('admin', 'posts.create')
@Post('posts')
createPost() { /* user must have the role 'admin' OR the permission 'posts.create' */ }

Important: Always put JwtAuthGuard (or your auth guard) before the permission/role guard so request.user is populated.

Using the Service Directly

Inject PermissionRegistrarService for programmatic access:

import {
  PermissionRegistrarService,
  PermissionService,
  RoleService,
} from "@nestbolt/permissions";

@Injectable()
export class SetupService {
  constructor(
    private readonly registrar: PermissionRegistrarService,
    private readonly permissionService: PermissionService,
    private readonly roleService: RoleService,
  ) {}

  async seedRolesAndPermissions() {
    // Create permissions
    await this.permissionService.create("posts.create");
    await this.permissionService.create("posts.edit");
    await this.permissionService.create("posts.delete");

    // Create roles and assign permissions
    await this.roleService.create("admin");
    await this.roleService.givePermissionTo(
      "admin",
      "posts.create",
      "posts.edit",
      "posts.delete",
    );

    await this.roleService.create("editor");
    await this.roleService.givePermissionTo(
      "editor",
      "posts.create",
      "posts.edit",
    );

    // Assign roles to users
    await this.registrar.assignRole("user-uuid", "admin");

    // Check permissions
    await this.registrar.userHasPermissionTo("user-uuid", "posts.create"); // true
    await this.registrar.userHasRole("user-uuid", "admin"); // true
  }
}

PermissionRegistrarService Methods

| Method | Returns | Description | | --------------------------------------- | ----------------------------- | ----------------------------------------------- | | assignRole(userId, ...roles) | Promise<void> | Assign roles to a user | | removeRole(userId, ...roles) | Promise<void> | Remove roles from a user | | syncRoles(userId, ...roles) | Promise<void> | Replace all user roles with given ones | | givePermissionTo(userId, ...perms) | Promise<void> | Give direct permissions to a user | | revokePermissionTo(userId, ...perms) | Promise<void> | Revoke direct permissions from a user | | syncPermissions(userId, ...perms) | Promise<void> | Replace all direct user permissions | | userHasRole(userId, role) | Promise<boolean> | Check if user has a specific role | | userHasAnyRole(userId, roles) | Promise<boolean> | Check if user has any of the roles | | userHasAllRoles(userId, roles) | Promise<boolean> | Check if user has all roles | | userHasPermissionTo(userId, perm) | Promise<boolean> | Check permission (direct + via role + wildcard) | | userHasAnyPermission(userId, perms) | Promise<boolean> | Check if user has any permission | | userHasAllPermissions(userId, perms) | Promise<boolean> | Check if user has all permissions | | userHasDirectPermission(userId, perm) | Promise<boolean> | Check only direct permission | | getUserRoleNames(userId) | Promise<string[]> | Get all role names for a user | | getUserAllPermissions(userId) | Promise<PermissionEntity[]> | Get all permissions (direct + via roles) | | getUserPermissionsViaRoles(userId) | Promise<PermissionEntity[]> | Get permissions inherited from roles | | getUserDirectPermissions(userId) | Promise<PermissionEntity[]> | Get directly assigned permissions | | flushCache() | void | Clear the permission cache |

PermissionService Methods

| Method | Returns | Description | | -------------------------------- | ----------------------------- | ---------------------------------- | | create(name, guardName?) | Promise<PermissionEntity> | Create a permission | | findByName(name, guardName?) | Promise<PermissionEntity> | Find by name (throws if not found) | | findById(id, guardName?) | Promise<PermissionEntity> | Find by ID (throws if not found) | | findOrCreate(name, guardName?) | Promise<PermissionEntity> | Find or create a permission | | findAll(guardName?) | Promise<PermissionEntity[]> | List all permissions | | findByNames(names, guardName?) | Promise<PermissionEntity[]> | Find multiple by names | | delete(nameOrId, guardName?) | Promise<void> | Delete a permission |

RoleService Methods

| Method | Returns | Description | | ------------------------------------ | ----------------------- | ---------------------------------- | | create(name, guardName?) | Promise<RoleEntity> | Create a role | | findByName(name, guardName?) | Promise<RoleEntity> | Find by name (throws if not found) | | findById(id, guardName?) | Promise<RoleEntity> | Find by ID (throws if not found) | | findOrCreate(name, guardName?) | Promise<RoleEntity> | Find or create a role | | findAll(guardName?) | Promise<RoleEntity[]> | List all roles | | delete(nameOrId, guardName?) | Promise<void> | Delete a role | | givePermissionTo(role, ...perms) | Promise<RoleEntity> | Attach permissions to a role | | revokePermissionTo(role, ...perms) | Promise<RoleEntity> | Detach permissions from a role | | syncPermissions(role, ...perms) | Promise<RoleEntity> | Replace all role permissions | | hasPermissionTo(role, perm) | Promise<boolean> | Check if role has a permission |

Implementing PermissionUserRepository

The PermissionUserRepository interface is the bridge between this package and your User entity. You must implement it and pass it to the module configuration.

| Method | Description | | ------------------------------------------ | ------------------------------------------- | | getDirectPermissionIds(userId) | Return IDs of directly assigned permissions | | getRoleIds(userId) | Return IDs of assigned roles | | attachRoles(userId, roleIds) | Insert user-role associations | | detachRoles(userId, roleIds) | Remove user-role associations | | detachAllRoles(userId) | Remove all roles from user | | attachPermissions(userId, permissionIds) | Insert user-permission associations | | detachPermissions(userId, permissionIds) | Remove user-permission associations | | detachAllPermissions(userId) | Remove all direct permissions from user | | userExists(userId) | Check if user exists |

See Quick Start for a full TypeORM implementation example.

Configuration Options

| Option | Type | Default | Description | | ------------------------------ | -------------------------------- | -------------- | ---------------------------------------------- | | userRepository | Type<PermissionUserRepository> | (required) | Class implementing PermissionUserRepository | | defaultGuardName | string | "default" | Default guard name for roles/permissions | | enableWildcardPermissions | boolean | false | Enable wildcard permission matching | | cache.enabled | boolean | true | Enable in-memory permission cache | | cache.ttl | number | 86400000 | Cache TTL in milliseconds (default: 24 hours) | | displayPermissionInException | boolean | false | Include permission names in 403 error messages | | displayRoleInException | boolean | false | Include role names in 403 error messages |

Features

Wildcard Permissions

Enable wildcard matching to define broad permissions:

PermissionsModule.forRoot({
  userRepository: TypeOrmPermissionUserRepository,
  enableWildcardPermissions: true,
});
// Give a user the wildcard permission
await permissionService.create("articles.*");
await registrar.givePermissionTo(userId, "articles.*");

// Now these checks all return true:
await registrar.userHasPermissionTo(userId, "articles.create"); // true
await registrar.userHasPermissionTo(userId, "articles.edit"); // true
await registrar.userHasPermissionTo(userId, "articles.delete"); // true

// Super-admin wildcard
await permissionService.create("*");
await registrar.givePermissionTo(userId, "*");
await registrar.userHasPermissionTo(userId, "anything.at.all"); // true

Events

If @nestjs/event-emitter is installed, the package emits events on role/permission changes:

| Event | Payload | When | | --------------------------------- | --------------------------- | ------------------------------- | | permissions.role-attached | { userId, roleIds } | Role(s) assigned to user | | permissions.role-detached | { userId, roleIds } | Role(s) removed from user | | permissions.permission-attached | { userId, permissionIds } | Permission(s) given to user | | permissions.permission-detached | { userId, permissionIds } | Permission(s) revoked from user | | permissions.role-created | { role } | New role created | | permissions.role-deleted | { role } | Role deleted | | permissions.permission-created | { permission } | New permission created | | permissions.permission-deleted | { permission } | Permission deleted | | permissions.cache-flushed | {} | Permission cache cleared |

Caching

The in-memory cache stores permission data to avoid repeated database queries. It is automatically flushed when roles or permissions are modified. You can also flush it manually:

registrar.flushCache();

Testing

npm test

Run tests in watch mode:

npm run test:watch

Generate coverage report:

npm run test:cov

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security-related issues, please report them via GitHub Issues with the security label instead of using the public issue tracker.

License

The MIT License (MIT). Please see License File for more information.