Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions _artifacts/domain_map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ domains:
slug: server
description: >
Per-request setupMiddleware and checkMiddleware for Express, Hono, Fastify,
tRPC, oRPC, Node, and Elysia.
NestJS, tRPC, oRPC, Node, and Elysia.

- name: 'SSR and hydration'
slug: ssr
Expand Down Expand Up @@ -78,7 +78,7 @@ skills:
~all/~any, entity-aware ReBAC rules, isReady/isReadyAsync;
PermixProvider/usePermix/createComponents and SSR dehydrate/hydrate for
React, Vue, Solid, Svelte, Next.js, TanStack Start; setupMiddleware and
checkMiddleware for Express, Hono, Fastify, tRPC, oRPC, Node, Elysia.
checkMiddleware for Express, Hono, Fastify, NestJS, tRPC, oRPC, Node, Elysia.
Single skill with a thin SKILL.md router and three reference files
loaded on demand.
type: core
Expand All @@ -94,6 +94,7 @@ skills:
- express
- hono
- fastify
- nest
- trpc
- orpc
- node
Expand Down Expand Up @@ -140,6 +141,7 @@ skills:
- 'letstri/permix:docs/content/docs/integrations/express.mdx'
- 'letstri/permix:docs/content/docs/integrations/hono.mdx'
- 'letstri/permix:docs/content/docs/integrations/fastify.mdx'
- 'letstri/permix:docs/content/docs/integrations/nest.mdx'
- 'letstri/permix:docs/content/docs/integrations/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
Expand Down
4 changes: 3 additions & 1 deletion _artifacts/skill_tree.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ skills:
isReady/isReadyAsync; PermixProvider/usePermix/createComponents and SSR
dehydrate/hydrate for React, Vue, Solid, Svelte, Next.js, TanStack
Start; setupMiddleware/checkMiddleware for Express, Hono, Fastify,
tRPC, oRPC, Node, Elysia. Thin router with references loaded on demand.
NestJS, tRPC, oRPC, Node, Elysia. Thin router with references loaded on demand.
requires:
- permix-getting-started
subsystems:
Expand All @@ -53,6 +53,7 @@ skills:
- express
- hono
- fastify
- nest
- trpc
- orpc
- node
Expand All @@ -75,6 +76,7 @@ skills:
- 'letstri/permix:docs/content/docs/integrations/express.mdx'
- 'letstri/permix:docs/content/docs/integrations/hono.mdx'
- 'letstri/permix:docs/content/docs/integrations/fastify.mdx'
- 'letstri/permix:docs/content/docs/integrations/nest.mdx'
- 'letstri/permix:docs/content/docs/integrations/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/comparison.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Built with `pnpm run build` in the `permix` package (`tsdown` for all entries ex
| `permix/hono` | 0.88 kB | adapter |
| `permix/fastify` | 1.04 kB | adapter |
| `permix/elysia` | 0.88 kB | adapter |
| `permix/nest` | 1.39 kB | adapter |
| `permix/trpc` | 0.96 kB | adapter |
| `permix/orpc` | 0.92 kB | adapter |
| `permix/effect` | 1.28 kB | adapter |
Expand Down
261 changes: 261 additions & 0 deletions docs/content/docs/integrations/nest.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
---
title: NestJS
description: Learn how to use Permix with NestJS
---

## Overview

Permix provides a NestJS integration that sets up permissions per request and enforces them with a guard plus a `@Check` decorator. The factory is created using `createPermix` from `permix/nest`.

<Callout>
Before getting started with the NestJS integration, make sure you've completed
the initial setup steps in the [Quick Start](/docs/quick-start) guide.
</Callout>

<Steps>

<Step>

## Setup

Create a Permix factory and register its guard as a global `APP_GUARD`. The guard always attaches a per-request instance; it only enforces a permission when `@Check` is present.

```ts
import { Module } from '@nestjs/common'
import { APP_GUARD } from '@nestjs/core'
import { createPermix } from 'permix/nest'

interface Post {
id: string
authorId: string
title: string
content: string
}

export const permix = createPermix<{
post: [
{ name: 'create'; type: Post },
{ name: 'read'; type: Post },
{ name: 'update'; type: Post },
]
}>()

@Module({
providers: [
{
provide: APP_GUARD,
useValue: permix.guard(({ req }) => {
// You can access req.user or other properties to determine permissions
return {
post: {
create: true,
read: true,
update: false,
},
}
}),
},
],
})
export class AppModule {}
```

<Callout>
The guard works with both the Express and Fastify Nest HTTP adapters. It
preserves full type safety from your Permix definition.
</Callout>

</Step>

<Step>

## Checking Permissions

Use the `Check` decorator on a handler or controller:

```ts
import { Controller, Delete, Get, Post, Put } from '@nestjs/common'
import { permix } from './permix'

@Controller('posts')
export class PostsController {
@Post()
@permix.Check('post.create')
create() {
return { success: true }
}

@Put(':id')
@permix.Check((c) => c('post.read') && c('post.update'))
update() {
return { success: true }
}

@Delete(':id')
@permix.Check('post.~all')
remove() {
return { success: true }
}

@Get()
@permix.Check('post.~any')
findAll() {
return { posts: getAllPosts() }
}
}
```

</Step>

<Step>

## Accessing Permix Directly

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

```ts
@Get()
findAll(@Req() req: Request) {
const { check } = permix.getOrThrow(req)

if (check('post.read')) {
return { posts: getAllPosts() }
}

throw new ForbiddenException({
error: 'You do not have permission to read posts',
})
}
```

Entity-based (ReBAC) checks usually run in the handler after the resource is loaded:

```ts
@Put(':id')
async update(@Param('id') id: string, @Req() req: Request) {
const post = await getPostById(id)
const { check } = permix.getOrThrow(req)

if (!check('post.update', post)) {
throw new ForbiddenException({ error: 'You cannot update this post' })
}

return { success: true }
}
```

</Step>

<Step>

## Using Templates

Permix provides a template helper to create reusable permission rule sets:

```ts
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
},
})

{
provide: APP_GUARD,
useValue: permix.guard(({ req }) => {
if (req.user?.role === 'admin') {
return adminTemplate()
}

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

</Step>

</Steps>

## Custom Error Handling

By default, a denied `@Check` throws a Nest `ForbiddenException` with `{ error: 'Forbidden' }`. You can customize this by providing an `onForbidden` handler:

### Basic Error Handler

```ts
const permix = createPermix<Definition>({
onForbidden: () => {
throw new ForbiddenException({
error: 'Custom forbidden message',
})
},
})
```

### Dynamic Error Handler

You can also throw different responses based on the checked path:

```ts
const permix = createPermix<Definition>({
onForbidden: ({ path }) => {
if (path === 'post.create') {
throw new ForbiddenException({
error: `You don't have permission for ${path}`,
})
}

throw new ForbiddenException({
error: 'You do not have permission to perform this action',
})
},
})
```

The `onForbidden` handler receives:

- `req`: The HTTP request object (Express or Fastify)
- `context`: Nest `ExecutionContext`
- `path`: The permission path that was checked (or `null` for callback checks)
- `data`: Optional entity data passed to the check

## Advanced Usage

### Async Permission Rules

You can use async functions in your guard setup:

```ts
permix.guard(async ({ req }) => {
const userPermissions = await getUserPermissions(req.user.id)

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

### Hooks

You can register hooks at the factory level to listen for events across all requests:

```ts
permix.hook('check', ({ path, data }) => {
console.log(`Permission checked: ${path}`, data)
})
```

### Example

You can find the example of the NestJS integration [here](https://github.com/letstri/permix/tree/main/examples/nest).
1 change: 1 addition & 0 deletions docs/content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"integrations/hono",
"integrations/elysia",
"integrations/fastify",
"integrations/nest",
"integrations/effect",
"integrations/drizzle",
"---",
Expand Down
Loading
Loading