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-permissionsh
npm install @actinode/express-permissionsh
yarn add @actinode/express-permissionThen install the adapter for your database:
sh
pnpm add @prisma/clientsh
pnpm add mongooseQuick 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
- Roles — assign, revoke, and check roles
- Permissions — direct permissions and canAny / canAll
- Permission Groups — bundle permissions together
- API Reference — full method and type reference
Community & Support
- 🐛 Found a bug? Open a bug report
- 🚀 Have a feature idea? Open a feature request
- 💬 Questions? Join the community
