Introduction
The type-safe permission management you've always needed
Idea
In my many years of experience, I have worked extensively with permissions management, and early in my career I wrote solutions that looked like this:
if (user.role === 'admin') {
// do something
}
Later, I started using CASL for permissions management in a Vue application.
can('read', ['Post', 'Comment']);
can('manage', 'Post', { author: 'me' });
can('create', 'Comment');
But time goes on, CASL becomes older, and developers' needs grow, especially for type-safe libraries. Unfortunately, CASL couldn't satisfy my type validation needs and so I started thinking again about writing my own validation solution. But this time I wanted to make it as a library, as I already had experience with open-source.
Implementation
I started to create my own solution. However, nothing occurred to me until I watched a Web Dev Simplified video where he demonstrated an example of implementing permission management as he envisioned it. I really liked his approach because it was based on type-safety, which is exactly what I needed.
So I'm ready to present to you my permission management solution called Permix!
DX
When creating Permix, the goal was to simplify DX as much as possible without losing type-safety and provide the necessary functionality.
That is why you only need to write the following code to get started:
import { createPermix } from 'permix'
const permix = createPermix<{
post: {
action: 'read'
}
}>()
permix.setup({
post: {
read: true,
}
})
const canReadPost = permix.check('post', 'read') // true
It looks too simple, so here's a more interesting example:
import type { PermixDefinition } from 'permix'
import { createPermix } from 'permix'
// You can take types from your database
interface User {
id: string
role: 'editor' | 'user'
}
interface Post {
id: string
title: string
authorId: string
published: boolean
}
interface Comment {
id: string
content: string
authorId: string
}
// Create definition to describe your permissions
type PermissionsDefinition = PermixDefinition<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
comment: {
dataType: Comment
action: 'create' | 'read' | 'update'
}
}>
const permix = createPermix<PermissionsDefinition>()
// Define permissions for different users
const editorPermissions = permix.template({
post: {
create: true,
read: true,
update: post => !post?.published,
delete: post => !post?.published,
},
comment: {
create: false,
read: true,
update: false,
},
})
const userPermissions = permix.template(({ id: userId }: User) => ({
post: {
create: false,
read: true,
update: false,
delete: false,
},
comment: {
create: true,
read: true,
update: comment => comment?.authorId === userId,
},
}))
async function getUser() {
// Imagine that this function is fetching user from database
return {
id: '1',
role: 'editor' as const,
}
}
// Setup permissions for signed in user
async function setupPermix() {
const user = await getUser()
const permissionsMap = {
editor: () => editorPermissions(),
user: () => userPermissions(user),
}
permix.setup(permissionsMap[user.role]())
}
// Call setupPermix where you need to setup permissions
setupPermix()
// Check if a user has permission to do something
const canCreatePost = permix.check('post', 'create')
async function getComment() {
// Imagine that this function is fetching comment from database
return {
id: '1',
content: 'Hello, world!',
authorId: '1',
}
}
const comment = await getComment()
const canUpdateComment = permix.check('comment', 'update', comment)
Benefits
What are the benefits of using Permix?
- 100% type-safe without writing TypeScript (except for initialization)
- Single source of truth for your entire app
- Perfect match for TypeScript monorepos
- Zero dependencies
- Useful methods for specific cases
- Large number of integrations for different frameworks, such as React, Vue, Express, and more.
Ready?
Ready to take Permix to your project? Let's go to the Quick Start page.