Permix

Fastify

Learn how to use Permix with Fastify

Overview

Permix provides a plugin for Fastify that allows you to easily check permissions in your routes. The plugin can be created using the createPermix function.

Before getting started with Fastify 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 plugin with Fastify:

import  from 'fastify'
import {  } from 'permix/fastify'

interface Post {
  : string
  : string
  : string
  : string
}

// Initialize Fastify
const  = ()

// Create your Permix instance
const  = <{
  : {
    : Post
    : 'create' | 'read' | 'update'
  }
}>()

// Set up the plugin with your permission rules
.(.(({ ,  }) => {
  // You can access request.user or other properties to determine permissions
  return {
    : {
      : true,
      : true,
      : false
    }
  }
}))

The plugin preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.

Checking Permissions

Use the checkHandler function in your Fastify routes to check permissions:

fastify.post('/posts', {
  preHandler: permix.checkHandler('post', 'create'),
}, (request, reply) => {
  reply.send({ success: true })
})

// Check multiple actions
fastify.put('/posts/:id', {
  preHandler: permix.checkHandler('post', ['read', 'update']),
}, (request, reply) => {
  reply.send({ success: true })
})

// Check all actions
fastify.delete('/posts/:id', {
  preHandler: permix.checkHandler('post', 'all'),
}, (request, reply) => {
  reply.send({ success: true })
})

Accessing Permix Directly

You can access the Permix instance directly in your route handlers using the get function:

fastify.get('/posts', (request, reply) => {
  const { check } = permix.get(request, reply)

  // Check permissions manually
  if (check('post', 'read')) {
    // User has permission to read posts
    reply.send({ posts: getAllPosts() })
  } else {
    reply.status(403).send({ error: 'You do not have permission to read posts' })
  }
})

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
  }
})

// Use the template in your middleware
fastify.register(permix.plugin(({ request }) => {
  // You can still customize the template based on request data
  if (request.user?.role === 'admin') {
    return adminTemplate
  }

  return {
    post: {
      create: false,
      read: true,
      update: false
    }
  }
}))

Custom Error Handling

By default, the plugin returns a 403 Forbidden response. You can customize this behavior by providing an onForbidden handler:

Basic Error Handler

const permix = createPermix<Definition>({
  onForbidden: ({ reply }) => {
    reply.status(403).send({
      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: ({ reply, entity, actions }) => {
    if (entity === 'post' && actions.includes('create')) {
      reply.status(403).send({
        error: `You don't have permission to ${actions.join('/')} a ${entity}`,
      })
      return
    }

    reply.status(403).send({
      error: 'You do not have permission to perform this action',
    })
  }
})

The onForbidden handler receives:

  • request: Fastify Request object
  • reply: Fastify Reply 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:

fastify.register(permix.plugin(async ({ request }) => {
  // Fetch user permissions from database
  const userPermissions = await getUserPermissions(request.user.id)

  return {
    post: {
      create: userPermissions.canCreatePosts,
      read: userPermissions.canReadPosts,
      update: userPermissions.canUpdatePosts
    }
  }
}))

Dynamic Data-Based Permissions

You can check permissions based on the specific data being accessed:

fastify.put('/posts/:id', {
  preHandler: async (request, reply) => {
    const post = await getPostById(request.params.id)

    const { check } = permix.get(request, reply)

    // Check if user can update this specific post
    if (check('post', 'update', post)) {
      return
    } else {
      reply.status(403).send({ error: 'You cannot update this post' })
    }
  }
})