permix
Version:
Permix is a lightweight, framework-agnostic, type-safe permissions management library for JavaScript applications on the client and server sides.
123 lines (95 loc) • 3.77 kB
Markdown
---
name: permix-server
description: >-
Protects HTTP/RPC routes with Permix server adapters: setupMiddleware,
checkMiddleware, per-request instance. Use with permix/express, hono, fastify,
trpc, orpc, node, or server middleware in backend apps.
type: core
library: permix
library_version: '4.1.1'
requires:
- permix-getting-started
- permix-check
sources:
- '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/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
- 'letstri/permix:docs/content/docs/integrations/server.mdx'
- 'letstri/permix:docs/content/docs/integrations/elysia.mdx'
- 'letstri/permix:permix/src/express/index.ts'
- 'letstri/permix:permix/src/hono/index.ts'
- 'letstri/permix:permix/src/trpc/index.ts'
---
# Permix — server middleware
Authorization must run on the server. Client checks are UX only.
Docs: https://permix.letstri.dev/docs/integrations/express
## Pattern (Express-style; similar for Hono, Fastify, Node)
Import from the framework subpath, not bare `permix`:
```ts
import { createPermix } from 'permix/express'
const permix = createPermix<{
post: [
{ name: 'create', type: Post },
{ name: 'read', type: Post },
{ name: 'update', type: Post },
]
}>()
```
### Attach rules per request
```ts
app.use(permix.setupMiddleware(async ({ req }) => {
const user = req.user
return {
post: {
create: true,
read: true,
update: post => post.authorId === user.id,
},
}
}))
```
`setupMiddleware` accepts either a `Rules<D>` object or `({ req, res, next }) => Rules<D>` (sync or async).
### Guard routes
```ts
app.post('/posts', permix.checkMiddleware('post.create'), createPostHandler,)
app.put('/posts/:id', permix.checkMiddleware(c => c('post.read') && c('post.update')), updatePostHandler,)
app.delete('/posts/:id', permix.checkMiddleware('post.~all'), // example: require all post rules
adminHandler,)
```
Denied requests default to `403` with `{ error: 'Forbidden' }`. Customize with `onForbidden` in `createPermix` options.
### Access instance in handlers
```ts
app.get('/posts/:id', (req, res) => {
const p = permix.getOrThrow(req)
if (p.check('post.read', post)) { /* ... */ }
})
```
## Package subpaths
| Framework | Import |
|-----------|--------|
| Express | `permix/express` |
| Hono | `permix/hono` |
| Fastify | `permix/fastify` |
| tRPC | `permix/trpc` |
| oRPC | `permix/orpc` |
| Generic HTTP | `permix/node` or `permix/server` |
| Elysia | `permix/elysia` |
| Effect | `permix/effect` — see integration docs |
| Drizzle ORM | `permix/drizzle` (and `permix/drizzle/legacy`) — see integration docs |
Use the same `D` schema shape as the client instance.
Effect and Drizzle are optional peer dependencies; follow https://permix.letstri.dev/docs/integrations/effect and https://permix.letstri.dev/docs/integrations/drizzle rather than inventing middleware patterns.
## tRPC / oRPC
Use the adapter’s procedure/middleware helpers so checks run before the handler body. See integration docs for middleware names.
## Templates on the server
```ts
const rules = permix.template(adminRules)()
app.use(permix.setupMiddleware(rules))
```
## Checklist
- [ ] `setupMiddleware` runs **before** `checkMiddleware` on protected routes
- [ ] Rules derived from authenticated `req.user` (or RPC context), not client headers alone
- [ ] Entity checks pass resource data when the action has `type` / `required: true`
- [ ] Same paths as frontend (`post.update`, not ad-hoc strings)