Node.js
Learn how to use Permix with Node.js HTTP servers
Overview
Permix provides middleware for Node.js HTTP servers that allows you to easily check permissions in your request handlers. The middleware can be created using the createPermix
function.
Before getting started with Node.js 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 a Node.js HTTP server:
import http from 'node:http'
import { createPermix } from 'permix/node'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Create an HTTP server
const server = http.createServer(async (req, res) => {
// Parse the URL
const url = new URL(req.url || '/', `http://${req.headers.host}`)
const path = url.pathname
const method = req.method || 'GET'
const context = { req, res }
// Setup Permix with permission rules
await permix.setupMiddleware(({ req }) => {
// Determine user permissions (e.g., from headers, auth token, etc.)
const isAdmin = req.headers['x-user-role'] === 'admin'
return {
post: {
create: true,
read: true,
update: isAdmin,
delete: isAdmin
}
}
})(context)
// Route handling
if (path === '/posts' && method === 'POST') {
await permix.checkMiddleware('post', 'create')(context)
}
else if (path.startsWith('/posts/') && method === 'PUT') {
await permix.checkMiddleware('post', ['read', 'update'])(context)
}
else if (path.startsWith('/posts/') && method === 'DELETE') {
await permix.checkMiddleware('post', 'delete')(context)
}
else {
res.statusCode = 404
res.end(JSON.({ error: 'Not found' }))
}
})
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/')
})
The middleware preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
Checking Permissions
Use the checkMiddleware
function to check permissions for specific routes:
// Check a single action
await permix.checkMiddleware('post', 'create')(context)
// Check multiple actions
await permix.checkMiddleware('post', ['read', 'update'])(context)
// Check all actions
await permix.checkMiddleware('post', 'all')(context)
Accessing Permix Directly
You can access the Permix instance directly in your request handlers using the get
function:
http.createServer((req, res) => {
const { check } = permix.get(req, res)
// User has permission to read posts
if (check('post', 'read')) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ posts: getAllPosts() }))
}
})
The get
function returns the Permix instance with available methods.
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,
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false,
delete: false
}
})
// Use templates in your middleware
permix.setupMiddleware(({ req }) => {
const isAdmin = req.headers['x-user-role'] === 'admin'
if (isAdmin) {
return adminTemplate
}
return userTemplate
})(context)
Custom Error Handling
By default, the middleware returns a 403 Forbidden response if the user doesn't have permission. You can customize this behavior by providing an onForbidden
handler:
Basic Error Handler
const permix = createPermix<Definition>({
onForbidden: ({ res }) => {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: 'Custom forbidden message' }))
}
})
Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
const permix = createPermix<Definition>({
onForbidden: ({ res, entity, actions }) => {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
if (entity === 'post' && actions.includes('create')) {
res.end(JSON.stringify({
error: `You don't have permission to ${actions.join('/')} a ${entity}`
}))
return
}
res.end(JSON.stringify({
error: 'You do not have permission to perform this action'
}))
}
})
The onForbidden
handler receives:
req
: Node.js IncomingMessage objectres
: Node.js ServerResponse objectentity
: The entity that was checkedactions
: Array of actions that were checked
Advanced Usage
Async Permission Rules
You can use async functions in your permission setup:
permix.setupMiddleware(async ({ req }) => {
// Extract user ID from request
const userId = req.headers['x-user-id']
// Fetch user permissions from database
const userPermissions = await getUserPermissions(userId)
return {
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts,
delete: userPermissions.canDeletePosts
}
}
})(context)
Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
http.createServer(async (req, res) => {
// Setup Permix middleware first...
// Extract post ID from URL
const url = new URL(req.url || '/', `http://${req.headers.host}`)
const pathParts = url.pathname.split('/')
const postId = pathParts[2] // e.g., /posts/123
if (req.method === 'PUT' && pathParts[1] === 'posts' && postId) {
// Fetch the post data
const post = await getPostById(postId)
// Get Permix instance
const { check } = permix.get(req, res)
// Check if user can update this specific post
if (check('post', 'update', post)) {
// Process update...
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ success: true }))
} else {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: 'You cannot update this post' }))
}
}
})
Integration with Web Frameworks
This integration is designed for raw Node.js HTTP servers. If you're using a web framework:
- For Express, use Permix Express integration
- For Hono, use Permix Hono integration