Skip to content

express-permission

@actinode/express-permission gives your Express app role-based access control — roles, direct permissions, and permission groups — with a fluent, readable API inspired by spatie/laravel-permission.

When to use it

  • You need to protect routes so only users with the right role or permission can access them
  • You want to assign permissions directly to users and through roles
  • You need to group multiple permissions together and assign the whole group at once
  • You want programmatic helpers to check, assign, and revoke permissions anywhere in your app

Installation

sh
pnpm add @actinode/express-permission
sh
npm install @actinode/express-permission
sh
yarn add @actinode/express-permission

Then install the adapter for your database:

sh
pnpm add @prisma/client
sh
pnpm add mongoose

Quick start

1. Add the schema

prisma
model Role {
  id          String   @id @default(cuid())
  name        String   @unique
  permissions Permission[]
  users       UserRole[]
  createdAt   DateTime @default(now())
}

model Permission {
  id    String  @id @default(cuid())
  name  String  @unique
  roles Role[]
  users UserPermission[]
}

model PermissionGroup {
  id          String   @id @default(cuid())
  name        String   @unique
  permissions String[]
  createdAt   DateTime @default(now())
}

model UserRole {
  userId    String
  roleId    String
  role      Role   @relation(fields: [roleId], references: [id])
  @@id([userId, roleId])
}

model UserPermission {
  userId       String
  permissionId String
  permission   Permission @relation(fields: [permissionId], references: [id])
  @@id([userId, permissionId])
}

model UserGroup {
  userId  String
  group   String
  @@id([userId, group])
}
ts
import mongoose from 'mongoose'

const UserRoleSchema = new mongoose.Schema({ userId: String, roleName: String })
UserRoleSchema.index({ userId: 1, roleName: 1 }, { unique: true })

const UserPermissionSchema = new mongoose.Schema({ userId: String, permissionName: String })
UserPermissionSchema.index({ userId: 1, permissionName: 1 }, { unique: true })

const UserGroupSchema = new mongoose.Schema({ userId: String, group: String })
UserGroupSchema.index({ userId: 1, group: 1 }, { unique: true })

export const models = {
  Role:            mongoose.model('Role',            new mongoose.Schema({ name: { type: String, unique: true }, permissions: [String] })),
  Permission:      mongoose.model('Permission',      new mongoose.Schema({ name: { type: String, unique: true } })),
  PermissionGroup: mongoose.model('PermissionGroup', new mongoose.Schema({ name: { type: String, unique: true }, permissions: [String] })),
  UserRole:        mongoose.model('UserRole',        UserRoleSchema),
  UserPermission:  mongoose.model('UserPermission',  UserPermissionSchema),
  UserGroup:       mongoose.model('UserGroup',       UserGroupSchema),
}

2. Create the permission instance

ts
import { createPermission, prismaAdapter } from '@actinode/express-permission'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export const permission = createPermission({
  adapter: prismaAdapter(prisma),
  getUserId: (req) => req.user?.id,
})
ts
import { createPermission, mongooseAdapter } from '@actinode/express-permission'
import { models } from './models.js'

export const permission = createPermission({
  adapter: mongooseAdapter(models),
  getUserId: (req) => req.user?.id,
})

3. Protect your routes

ts
import express from 'express'
import { permission } from './permission.js'

const app = express()

// Require a permission
app.get('/posts',
  permission.can('view-posts'),
  (req, res) => res.json({ posts: [] }),
)

// Require a role
app.delete('/posts/:id',
  permission.hasRole('admin'),
  (req, res) => res.json({ deleted: true }),
)

// Any one of multiple permissions
app.put('/posts/:id',
  permission.canAny(['edit-posts', 'manage-posts']),
  (req, res) => res.json({ updated: true }),
)

4. Seed roles and permissions

ts
// Create a group of permissions
await permission.groups.create('content-managers', [
  'view-posts',
  'edit-posts',
  'publish-posts',
])

// Assign a role and a group to a user
await permission.assign.role(userId, 'editor')
await permission.assign.group(userId, 'content-managers')

What's next

Community & Support

Need an admin panel?

Get a beautiful admin UI for all actinode packages. Contact us to learn more about our premium admin package.

No spam, ever. Unsubscribe at any time.

Released under the MIT License.