A friendlier TypeScript wrapper around Permify.
Schema as code, a NestJS module that gets out of your way, and a CLI for the boring parts.
Permify is great, but using it from a Node.js app with Permify node client often ends up being more work than it should be.
In most of my NestJS projects, I kept writing the same setup again and again: wiring the client, keeping a separate .perm file, and maintaining small scripts to push schemas and seed data. The configuration between the app and those scripts would eventually drift, and fixing that was always annoying.
permify-toolkit is what I wish I had from the start. It keeps the schema in TypeScript, uses a single config for both the app and the CLI, and removes the need for custom scripts.
If it saves you an afternoon, that's the whole point. PRs and bug reports are welcome.
Read the complete documentation at thisisnkc.github.io/permify-toolkit.
permify-toolkit is a small monorepo of three packages:
| Package | What it does |
|---|---|
@permify-toolkit/core |
The schema DSL, a typed Permify client, and a shared config loader. Use this on its own if you don't use NestJS. |
@permify-toolkit/nestjs |
A NestJS module, a guard, and a @CheckPermission() decorator with AND/OR logic. |
@permify-toolkit/cli |
permify-toolkit schema push, relationship seeding, and a few other chores. |
You can pick and choose. The CLI and NestJS module both read the same permify.config.ts, so there's no duplication between dev workflow and runtime.
pnpm add @permify-toolkit/core
pnpm add @permify-toolkit/nestjs
pnpm add @permify-toolkit/clinpm and yarn are also supported.
1. Write your schema in TypeScript, not in a .perm file. (although you can use .perm files too)
// permify.config.ts
import {
defineConfig,
schema,
entity,
relation,
permission
} from "@permify-toolkit/core";
export default defineConfig({
tenant: "t1",
client: { endpoint: "localhost:3478", insecure: true },
schema: schema({
user: entity({}),
document: entity({
relations: { owner: relation("user") },
permissions: {
edit: permission("owner"),
view: permission("owner")
}
})
})
});2. Push it to Permify with CLI.
permify-toolkit schema push
permify-toolkit relationships seed --file-path ./data/relationships.json3. Wire it into NestJS.
// app.module.ts
PermifyModule.forRoot({
configFile: true,
resolvers: {
subject: (ctx) => ctx.switchToHttp().getRequest().user?.id
}
});// documents.controller.ts
@Get(":id")
@CheckPermission({
resource: "document",
action: "view",
resourceId: (req) => req.params.id,
})
findOne(@Param("id") id: string) {
return this.documentsService.findOne(id);
}That's the whole loop. Define, push, decorate.
The most flexible way is environment variables, which clientOptionsFromEnv() reads for you:
import {
createPermifyClient,
clientOptionsFromEnv
} from "@permify-toolkit/core";
const client = createPermifyClient(clientOptionsFromEnv());It looks for PERMIFY_ENDPOINT, PERMIFY_INSECURE, PERMIFY_TLS_CERT, PERMIFY_TLS_KEY, PERMIFY_TLS_CA, and PERMIFY_AUTH_TOKEN. If you'd rather use a different prefix (say, your app name), pass it: clientOptionsFromEnv("MY_APP_").
If you'd rather pass options directly:
import * as fs from "node:fs";
import { createPermifyClient } from "@permify-toolkit/core";
const client = createPermifyClient({
endpoint: "permify.internal:3478",
insecure: false,
tls: {
cert: fs.readFileSync("cert.pem"),
key: fs.readFileSync("key.pem"),
ca: fs.readFileSync("ca.pem")
},
interceptor: { authToken: process.env.PERMIFY_AUTH_TOKEN },
timeoutMs: 60_000
});pnpm testThere are a few helpers if you want to drill in:
pnpm test:file client.spec.ts
pnpm test:group "Client Creation"
pnpm test:grep "should create a client"You can check the roadmap for what’s coming next. If you have an idea, open an issue or start a discussion.
Read CONTRIBUTING.md before opening a PR. Short version: be kind, keep changes focused, and run pnpm lint && pnpm test first.
MIT, see LICENSE.
If permify-toolkit saves you time, please ⭐ the repo. It helps others find it.
Made with ❤️ by Nikhil (thisisnkc).
