<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikhil</title>
    <description>The latest articles on DEV Community by Nikhil (@thisisnkc).</description>
    <link>https://dev.to/thisisnkc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3904911%2F4cd5d5b0-9c1c-4808-97c2-7eface6e8c72.png</url>
      <title>DEV Community: Nikhil</title>
      <link>https://dev.to/thisisnkc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thisisnkc"/>
    <language>en</language>
    <item>
      <title>Fine-Grained Authorization in NestJS Without the Boilerplate (A TypeScript Toolkit for Permify)</title>
      <dc:creator>Nikhil</dc:creator>
      <pubDate>Wed, 29 Apr 2026 19:19:52 +0000</pubDate>
      <link>https://dev.to/thisisnkc/fine-grained-authorization-in-nestjs-without-the-boilerplate-a-typescript-toolkit-for-permify-kk5</link>
      <guid>https://dev.to/thisisnkc/fine-grained-authorization-in-nestjs-without-the-boilerplate-a-typescript-toolkit-for-permify-kk5</guid>
      <description>&lt;h2&gt;
  
  
  Fine-Grained Authorization in NestJS Without the Boilerplate
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A practical look at how &lt;code&gt;permify-toolkit&lt;/code&gt; removes the friction of using Permify in a TypeScript and NestJS project.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I have been working with &lt;strong&gt;NestJS&lt;/strong&gt; for a few years now, and for most of that time, &lt;em&gt;authorization&lt;/em&gt; was the part I dreaded.&lt;/p&gt;

&lt;p&gt;Not authentication. That part is boring in a good way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization.&lt;/strong&gt; The part where you have to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Can this specific user actually do this specific thing to this specific resource?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...at runtime, correctly, without it becoming a maze of &lt;code&gt;if&lt;/code&gt; statements scattered across your controllers.&lt;/p&gt;

&lt;p&gt;I eventually landed on &lt;strong&gt;Permify&lt;/strong&gt; as the answer. If you have not heard of it, Permify is an &lt;em&gt;open-source, Google Zanzibar-inspired&lt;/em&gt; authorization service. You define your permission model as a schema, push it to the Permify server, and then check permissions over gRPC from your app. It is genuinely powerful, it scales, and the model is clean once you understand it.&lt;/p&gt;

&lt;p&gt;The problem was the &lt;strong&gt;integration layer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Raw Permify in Node.js
&lt;/h2&gt;

&lt;p&gt;The Permify Node client exists, but using it in a real NestJS app &lt;em&gt;without any abstraction&lt;/em&gt; means you are doing a lot of repetitive work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually wiring the &lt;strong&gt;gRPC client&lt;/strong&gt; in your module&lt;/li&gt;
&lt;li&gt;Keeping a separate &lt;code&gt;.perm&lt;/code&gt; file that lives outside your TypeScript codebase&lt;/li&gt;
&lt;li&gt;Writing small scripts to push schemas and seed relationships during development&lt;/li&gt;
&lt;li&gt;Watching the config between your app and those scripts &lt;em&gt;drift apart&lt;/em&gt; over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I kept rebuilding the same setup across projects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The NestJS module wiring.&lt;/li&gt;
&lt;li&gt;The schema push script.&lt;/li&gt;
&lt;li&gt;The seed script.&lt;/li&gt;
&lt;li&gt;The shared config that &lt;em&gt;inevitably&lt;/em&gt; became two separate configs because someone edited one and forgot the other.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I got tired of it. So I built &lt;strong&gt;&lt;code&gt;permify-toolkit&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is permify-toolkit?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;permify-toolkit&lt;/code&gt; is a small &lt;strong&gt;monorepo&lt;/strong&gt; of three TypeScript packages that work together to make Permify feel &lt;em&gt;native&lt;/em&gt; inside a NestJS project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@permify-toolkit/core&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Schema DSL in TypeScript, typed client factory, shared config loader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@permify-toolkit/nestjs&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NestJS module, guard, and &lt;code&gt;@CheckPermission()&lt;/code&gt; decorator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@permify-toolkit/cli&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CLI commands for schema push, relationship seeding, and more&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;key idea&lt;/strong&gt; is &lt;em&gt;one config file&lt;/em&gt;. You write a &lt;code&gt;permify.config.ts&lt;/code&gt; once, and both your NestJS app and the CLI read from the same file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;No duplication. No drift.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/thisisnkc/permify-toolkit" rel="noopener noreferrer"&gt;github.com/thisisnkc/permify-toolkit&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing Your Schema in TypeScript, Not in a &lt;code&gt;.perm&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;This was the &lt;em&gt;first&lt;/em&gt; thing I wanted to change. Permify schemas are written in a DSL called &lt;code&gt;.perm&lt;/code&gt;. It works, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It sits &lt;strong&gt;outside&lt;/strong&gt; your TypeScript project&lt;/li&gt;
&lt;li&gt;It has &lt;strong&gt;no type checking&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;one more thing&lt;/strong&gt; to keep in sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;permify-toolkit&lt;/code&gt;, you write your schema in TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;permission&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@permify-toolkit/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:3478&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt;
    &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;relations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner | viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is your &lt;code&gt;permify.config.ts&lt;/code&gt;. It is the &lt;strong&gt;single source of truth&lt;/strong&gt; for your entire Permify setup, both for the CLI and for the NestJS module at runtime.&lt;/p&gt;

&lt;p&gt;The schema DSL is &lt;em&gt;fully type-safe&lt;/em&gt;. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Autocomplete&lt;/strong&gt; on relation names, permission names, and entity references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile-time errors&lt;/strong&gt; if you rename an entity and forget to update a permission that references it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something is wrong, TypeScript tells you &lt;em&gt;before&lt;/em&gt; you push anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pushing Your Schema with the CLI
&lt;/h2&gt;

&lt;p&gt;Once your config is set up, pushing the schema to your Permify server is &lt;strong&gt;one command&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx permify-toolkit schema push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;No custom script.&lt;/em&gt; &lt;em&gt;No manually calling the gRPC schema write endpoint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The CLI reads your &lt;code&gt;permify.config.ts&lt;/code&gt;, converts the TypeScript schema to the &lt;code&gt;.perm&lt;/code&gt; format, and pushes it.&lt;/p&gt;

&lt;p&gt;You can also &lt;strong&gt;seed relationships&lt;/strong&gt; during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx permify-toolkit relationships seed &lt;span class="nt"&gt;--file-path&lt;/span&gt; ./data/relationships.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;em&gt;particularly useful&lt;/em&gt; in CI or local setup scripts where you want to provision a fresh Permify instance with test data before running integration tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring It Into NestJS
&lt;/h2&gt;

&lt;p&gt;This is where it starts to feel &lt;em&gt;really clean&lt;/em&gt;. Add the module to your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PermifyModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@permify-toolkit/nestjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;PermifyModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToHttp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;resolvers.subject&lt;/code&gt; function is how the module knows &lt;strong&gt;which user&lt;/strong&gt; to check permissions for. You point it at your request object, return the user ID, and the guard takes care of the rest.&lt;/p&gt;

&lt;p&gt;Then on &lt;strong&gt;any controller route&lt;/strong&gt;, you add a decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;CheckPermission&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That is the whole integration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The guard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercepts the request&lt;/li&gt;
&lt;li&gt;Resolves the subject from your resolver&lt;/li&gt;
&lt;li&gt;Checks the permission against Permify&lt;/li&gt;
&lt;li&gt;Either lets the request through &lt;em&gt;or&lt;/em&gt; throws a &lt;code&gt;403&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Combining Permissions with AND / OR Logic
&lt;/h3&gt;

&lt;p&gt;You can also combine permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;CheckPermission&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspaceId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;strong&gt;all conditions must pass&lt;/strong&gt; (&lt;code&gt;AND&lt;/code&gt;). You can switch to &lt;code&gt;OR&lt;/code&gt; mode if any one permission should be enough to grant access.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting to Permify
&lt;/h2&gt;

&lt;p&gt;For connecting to the Permify server, the most flexible approach is &lt;strong&gt;environment variables&lt;/strong&gt;. The &lt;code&gt;clientOptionsFromEnv()&lt;/code&gt; helper reads them for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPermifyClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientOptionsFromEnv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@permify-toolkit/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPermifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clientOptionsFromEnv&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It picks up the following automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PERMIFY_ENDPOINT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PERMIFY_INSECURE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PERMIFY_AUTH_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;TLS cert paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use a &lt;em&gt;custom env prefix&lt;/em&gt; for your app, you can pass it in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;clientOptionsFromEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MY_APP_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well in environments where you are managing secrets through a platform like &lt;strong&gt;Kubernetes&lt;/strong&gt;, &lt;strong&gt;Render&lt;/strong&gt;, or &lt;strong&gt;Railway&lt;/strong&gt;, and you do not want to hardcode connection details in your config file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for Teams
&lt;/h2&gt;

&lt;p&gt;The single-config design solves a &lt;em&gt;real coordination problem&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In most codebases, you have &lt;strong&gt;three separate things&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Permify schema definition lives in one place&lt;/li&gt;
&lt;li&gt;The script that pushes it lives somewhere else&lt;/li&gt;
&lt;li&gt;The NestJS module config is a third thing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As the team grows and the schema evolves, these three things &lt;strong&gt;drift apart&lt;/strong&gt;. You end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A production schema that does not quite match what the guard is checking&lt;/li&gt;
&lt;li&gt;A seed script that references entity types that were renamed three months ago&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When everything reads from &lt;code&gt;permify.config.ts&lt;/code&gt;, there is &lt;strong&gt;one place to update&lt;/strong&gt;. The CLI, the NestJS module, and your tests all work from the same definitions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Refactoring an entity name is a TypeScript rename, not a multi-file search-and-replace across config formats.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install the packages&lt;/strong&gt; with &lt;code&gt;npm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @permify-toolkit/core @permify-toolkit/nestjs
npm &lt;span class="nb"&gt;install&lt;/span&gt; @permify-toolkit/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with &lt;code&gt;pnpm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @permify-toolkit/core @permify-toolkit/nestjs @permify-toolkit/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create your &lt;code&gt;permify.config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Push your schema with the CLI&lt;/li&gt;
&lt;li&gt;Add the NestJS module&lt;/li&gt;
&lt;li&gt;Start decorating your routes&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full documentation:&lt;/strong&gt; &lt;a href="https://thisisnkc.github.io/permify-toolkit" rel="noopener noreferrer"&gt;thisisnkc.github.io/permify-toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;a href="https://github.com/thisisnkc/permify-toolkit" rel="noopener noreferrer"&gt;github.com/thisisnkc/permify-toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run into issues or want to suggest something, the repo is the right place.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Permify is a genuinely great solution&lt;/strong&gt; for fine-grained authorization. The Google Zanzibar model is the &lt;em&gt;right one&lt;/em&gt; for most modern SaaS applications where access control is relationship-based and cannot be expressed cleanly with simple role checks.&lt;/p&gt;

&lt;p&gt;The goal of &lt;code&gt;permify-toolkit&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; to add features on top of Permify. It is to &lt;em&gt;remove the friction&lt;/em&gt; of using Permify in a TypeScript and NestJS project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Less boilerplate&lt;/li&gt;
&lt;li&gt;✅ One config&lt;/li&gt;
&lt;li&gt;✅ Type-safe schemas&lt;/li&gt;
&lt;li&gt;✅ A guard that just works&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If it saves you an afternoon of setup, that is exactly what it was built for.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/thisisnkc/permify-toolkit" rel="noopener noreferrer"&gt;https://github.com/thisisnkc/permify-toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://thisisnkc.github.io/permify-toolkit" rel="noopener noreferrer"&gt;https://thisisnkc.github.io/permify-toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you find it useful, a ⭐ on the repo goes a long way in helping others discover it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>permify</category>
    </item>
  </channel>
</rss>
