Skip to content

Roles ​

A role is a named label — admin, editor, moderator — that you attach to users. A role can carry any number of permissions. When you check permission.can('edit-posts'), the package automatically checks all permissions assigned to every role the user holds.

Creating roles ​

Roles are created automatically the first time you assign them. There is no need to pre-register them.

ts
// This creates the 'admin' role if it doesn't exist, then assigns it to the user
await permission.assign.role(userId, 'admin')

Assigning roles to users ​

ts
await permission.assign.role(userId, 'admin')
await permission.assign.role(userId, 'editor')
await permission.assign.role(userId, 'moderator')

Assigning a role the user already has is safe — it is a no-op.

Revoking roles ​

ts
await permission.revoke.role(userId, 'editor')

WARNING

Revoking a role that was never assigned (or does not exist) throws a PermissionError with code NOT_FOUND. Wrap in try/catch if unsure.

ts
import { PermissionError } from '@actinode/express-permission'

try {
  await permission.revoke.role(userId, 'ghost-role')
} catch (err) {
  if (err instanceof PermissionError) {
    console.log(err.code)    // 'NOT_FOUND'
    console.log(err.message) // 'Role "ghost-role" not found'
  }
}

Getting all roles for a user ​

ts
const roles = await permission.get.roles(userId)
// ['admin', 'editor']

Checking roles in middleware ​

Use permission.hasRole() to protect a route so that only users with that role can access it.

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

// Only admins
app.delete('/users/:id',
  permission.hasRole('admin'),
  async (req, res) => {
    await deleteUser(req.params.id)
    res.json({ deleted: true })
  },
)

// Only moderators
app.post('/posts/:id/hide',
  permission.hasRole('moderator'),
  async (req, res) => {
    await hidePost(req.params.id)
    res.json({ hidden: true })
  },
)

Unauthorized (no userId in request):

json
{ "error": "Unauthorized", "message": "User not authenticated" }

HTTP status: 401

Forbidden (user lacks the role):

json
{ "error": "Forbidden", "message": "Missing role: admin" }

HTTP status: 403

Checking roles via helpers ​

Use permission.check.hasRole() outside of middleware — for example, in business logic or service functions.

ts
const isAdmin = await permission.check.hasRole(userId, 'admin')

if (isAdmin) {
  // perform privileged action
}

Assigning permissions to a role ​

Permissions on roles are managed at the database/adapter level. With the Prisma adapter you can link a permission to a role directly in your seed script or migration:

ts
// Seed: ensure the role + permission records exist, then link them
const adminRole = await prisma.role.upsert({
  where:  { name: 'admin' },
  create: { name: 'admin' },
  update: {},
})

const perm = await prisma.permission.upsert({
  where:  { name: 'delete-posts' },
  create: { name: 'delete-posts' },
  update: {},
})

await prisma.role.update({
  where: { id: adminRole.id },
  data:  { permissions: { connect: { id: perm.id } } },
})

With the Mongoose adapter, the Role document stores permissions as a String[]:

ts
await Role.findOneAndUpdate(
  { name: 'admin' },
  { $addToSet: { permissions: 'delete-posts' } },
  { upsert: true },
)

Once a permission is attached to the role, any user with that role will pass permission.can('delete-posts') and permission.check.can(userId, 'delete-posts') automatically.

Real-world example ​

ts
// Seed roles with permissions
const roles = {
  admin:     ['view-posts', 'edit-posts', 'delete-posts', 'manage-users'],
  editor:    ['view-posts', 'edit-posts', 'publish-posts'],
  viewer:    ['view-posts'],
}

for (const [roleName, perms] of Object.entries(roles)) {
  // ... upsert role and link permissions
}

// Assign roles to users at registration or promotion
await permission.assign.role(newUserId, 'viewer')
await permission.assign.role(promotedUserId, 'editor')

Released under the MIT License.