oRPC
Learn how to use Permix with oRPC
Overview
Permix provides a middleware for oRPC that allows you to easily check permissions in your middlewares. The middleware can be created using the createPermix function.
Before getting started with oRPC integration, make sure you've completed the initial setup steps in the Quick Start guide.
Setup
Here's a basic example of how to use the Permix middleware with oRPC:
import { os } from '@orpc/server'
import { createPermix } from 'permix/orpc'
interface Post {
id: string
title: string
}
interface Context {
user: {
id: string
role: string
}
}
const orpcPermix = os.$context<Context>()
// Create your Permix instance with a custom context key
const permix = createPermix<{
post: ['create', 'read', 'update']
user: ['delete']
}>().contextKey('permissions')
// Create a protected middleware with Permix
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
const isAdmin = context.user.role === 'admin'
return next({
context: permix.setupContext({
post: {
create: true,
read: true,
update: isAdmin
},
user: {
delete: isAdmin
}
})
})
})Call .contextKey('name') on the returned builder to set a custom context key — its literal type is inferred automatically. Omit it to use the default key 'permix'. Pass onForbidden as an option to createPermix itself.
Checking Permissions
Use the checkMiddleware function in your oRPC middlewares to check permissions:
const router = orpcPermix.router({
createPost: protectedMiddleware
.use(permix.checkMiddleware('post.create'))
.handler(({ context }) => {
// Create post logic here
return { success: true }
}),
updatePost: protectedMiddleware
.use(permix.checkMiddleware(c => c('post.read') && c('post.update')))
.handler(({ context }) => {
// Update post logic here
return { success: true }
}),
deleteUser: protectedMiddleware
.use(permix.checkMiddleware('user.delete'))
.handler(({ context }) => {
// Delete user logic here
return { success: true }
})
})Accessing Permix in Middlewares
Permix is automatically added to your oRPC context under the key you specified, so you can access it directly:
const router = orpcPermix.router({
getPosts: protectedMiddleware
.handler(({ context }) => {
// Check permissions manually
if (context.permissions.check('post.read')) {
return getAllPosts()
}
throw new ORPCError('FORBIDDEN', {
message: 'You do not have permission to read posts'
})
})
})The context.permissions object provides:
check: Synchronously check a permissiondehydrate: Serialize the current rules for client hydrationtemplate: Create reusable permission templates
Using Templates
Permix provides a template helper to create reusable permission rule sets:
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true
},
user: {
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false
},
user: {
delete: false
}
})
// Use templates in your middleware
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
return next({
context: permix.setupContext(
context.user.role === 'admin'
? adminTemplate()
: userTemplate()
),
})
})Custom Error Handling
By default, the middleware throws an ORPCError with code FORBIDDEN. You can customize this behavior with the onForbidden option, which is a terminal handler — it receives the middleware opts (including next) plus the check context, and controls the outcome:
Throw a Custom Error
const permix = createPermix<Definition>({
onForbidden: ({ path, context }) => {
throw new ORPCError('FORBIDDEN', {
message: `User ${context.user.id} doesn't have permission for ${path}`
})
}
})Allow Through
You can also let denied requests through by calling next():
const permix = createPermix<Definition>({
onForbidden: ({ path, next }) => {
console.warn(`Permission denied for ${path}, allowing through`)
return next()
}
})The onForbidden handler receives:
path: The permission path that was checked (e.g.'post.create')data: Optional data passed to the checkcontext: Your oRPC context objectnext: The middlewarenextfunction — call it to allow the request through
Advanced Usage
Async Permission Rules
You can use async functions in your permission setup:
const protectedMiddleware = orpcPermix.use(async ({ context, next }) => {
const userPermissions = await getUserPermissions(context.user.id)
return next({
context: permix.setupContext({
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts
},
user: {
delete: userPermissions.canDeleteUsers
}
})
})
})Hooks
You can register hooks at the factory level to listen for events across all requests:
permix.hook('check', ({ path, data }) => {
console.log(`Permission checked: ${path}`, data)
})