Backend
Roles
Map roles to permissions and define role hierarchies for access control.
Roles group permissions into named sets. Each user gets a role, and the engine resolves which permissions apply based on that role.
Example
An orders dashboard with three roles — viewer, editor, and admin:
import { createEngine } from '@superapp/backend'
const engine = createEngine({
connections: {
main: { type: 'postgres', url: process.env.PG_URL! },
},
permissions: {
view_orders: { /* ... */ },
edit_orders: { /* ... */ },
delete_orders: { /* ... */ },
},
roles: {
viewer: ['view_orders'],
editor: ['view_orders', 'edit_orders'],
admin: ['view_orders', 'edit_orders', 'delete_orders'],
},
})Each role is an array of permission slugs. Higher roles include lower-role permissions — admin can do everything editor can, plus delete orders.
How It Works
- User authenticates and
resolveSessionreturns their role - Engine looks up the role in the
rolesconfig - All permissions in that role's array are activated for the request
Role Resolution
The role comes from the user's session. Set it up in resolveSession:
const auth = betterAuthProvider({
secret: process.env.AUTH_SECRET!,
userTable: {
table: 'main.users',
matchOn: { column: 'id', jwtField: 'id' },
},
resolveSession: async (user, db) => {
const membership = await db
.selectFrom('main.members')
.select(['organization_id', 'role'])
.where('user_id', '=', user.id)
.where('status', '=', 'active')
.executeTakeFirst()
return {
...user,
role: membership?.role ?? 'viewer',
current_org_id: membership?.organization_id ?? null,
}
},
})Unknown Roles
If a user's role is not in the roles config, they get zero permissions — all data requests return empty results or 403 Forbidden.