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 { } from 'permix'
const = <{
: ['read']
}>()
.({
: {
: true,
},
})
const = .('post.read') // trueIt looks too simple, so here's a more interesting example:
import { } from 'permix'
// You can take types from your database
interface User {
: string
: 'editor' | 'user'
}
interface Post {
: string
: string
: string
: boolean
}
interface Comment {
: string
: string
: string
}
// Create definition to describe your permissions
type = {
: [
{ : 'create'; : Post },
{ : 'read'; : Post },
{ : 'update'; : Post },
{ : 'delete'; : Post },
]
: [
{ : 'create'; : Comment },
{ : 'read'; : Comment },
{ : 'update'; : Comment },
]
}
const = <>()
// Define permissions for different users
const = .({
: {
: true,
: true,
: () => !?.,
: () => !?.,
},
: {
: false,
: true,
: false,
},
})
const = .(({ : }: User) => ({
: {
: false,
: true,
: false,
: false,
},
: {
: true,
: true,
: () => ?. === ,
},
}))
async function () {
// Imagine that this function is fetching user from database
return {
: '1',
: 'editor' as ,
}
}
// Setup permissions for signed in user
async function () {
const = await ()
const = {
: () => (),
: () => (),
}
.([.]())
}
// Call setupPermix where you need to setup permissions
()
// Check if a user has permission to do something
const = .('post.create')
async function () {
// Imagine that this function is fetching comment from database
return {
: '1',
: 'Hello, world!',
: '1',
}
}
const = await ()
const = .('comment.update', )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
- Built-in SSR via dehydrate / hydrate
- Relationship-based rules with closures — see ReBAC patterns
- Large number of integrations for different frameworks, such as React, Vue, Express, and more.
Core concepts
Instance
Describe the permission schema and create a Permix instance.
Setup
Assign boolean or function rules for the current user.
Check
Check permissions with full type inference on paths and data.
Template
Reusable role presets that stay in sync with the schema.
Hydration
Transfer rules from the server to the client for SSR.
Ready state
Wait for permissions before rendering with isReady().
Events
React to setup, ready, and check with typed hooks.
ReBAC
Relationship-based rules with closures and data-based checks.
Ready?
Ready to take Permix to your project? Let's go to the Quick Start page.
Last updated on