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.