Permix

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  from 'node:http'
import {  } from 'permix/node'
 
interface Post {
  : string
  : string
  : string
  : string
}
 
// Create your Permix instance
const  = <{
  : {
    : Post
    : 'create' | 'read' | 'update' | 'delete'
  }
}>()
 
// Create an HTTP server
const  = .(async (, ) => {
  // Parse the URL
  const  = new (. || '/', `http://${..}`)
  const  = .
  const  = . || 'GET'
 
  const  = { ,  }
 
  // Setup Permix with permission rules
  await .(({  }) => {
    // Determine user permissions (e.g., from headers, auth token, etc.)
    const  = .['x-user-role'] === 'admin'
 
    return {
      : {
        : true,
        : true,
        : ,
        : 
      }
    }
  })()
 
  // Route handling
  if ( === '/posts' &&  === 'POST') {
    await .('post', 'create')()
  }
  else if (.('/posts/') &&  === 'PUT') {
    await .('post', ['read', 'update'])()
  }
  else if (.('/posts/') &&  === 'DELETE') {
    await .('post', 'delete')()
  }
  else {
    . = 404
    .(.({ : 'Not found' }))
  }
})
 
.(3000, () => {
  .('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 object
  • res: Node.js ServerResponse object
  • entity: The entity that was checked
  • actions: 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:

On this page