<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>ZenStack Blog</title>
        <link>https://zenstack.dev/blog</link>
        <description>ZenStack Blog</description>
        <lastBuildDate>Thu, 02 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Prisma vs Drizzle vs ZenStack: Choosing a TypeScript ORM in 2026]]></title>
            <link>https://zenstack.dev/blog/orm-2026</link>
            <guid>https://zenstack.dev/blog/orm-2026</guid>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Prisma, Drizzle, or ZenStack? A practical 2026 comparison covering querying, access control, API integration, extensibility, and how to choose the right TypeScript ORM for your project.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover image" src="https://zenstack.dev/assets/images/cover-34686e54bcafd18d2497d3e8b6c7710f.png" width="1424" height="606" class="img_XvmG"></p>
<p>If you've built a TypeScript backend in the last few years, you've probably used <a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a>. It earned its place as the default choice — a clean schema language, an intuitive query API, and type safety that felt almost magical at the time.</p>
<p>But the TypeScript ORM space has quietly matured. <a href="https://orm.drizzle.team/" target="_blank" rel="noopener noreferrer">Drizzle</a> emerged as a serious alternative for developers who felt Prisma was too opinionated. And <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> v3 just completed a full rewrite that positions it not just as an ORM, but as a full-stack data layer.</p>
<p>Three tools, three distinct bets on what "great database access" means in 2026. This post breaks them down so you can pick the right one for your project.</p>
<blockquote>
<p><em>Disclosure: This post is published by the ZenStack team. We've done our best to represent Prisma and Drizzle fairly, but you should factor that context into how you weigh the comparisons.</em></p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="1-the-three-philosophies">1. The Three Philosophies<a class="hash-link" aria-label="Direct link to 1. The Three Philosophies" title="Direct link to 1. The Three Philosophies" href="https://zenstack.dev/blog/orm-2026#1-the-three-philosophies">​</a></h2>
<p>Before comparing features and APIs, it helps to understand what each tool is actually optimizing for.</p>
<p><strong>Prisma</strong> is schema-first and proud of it. You define your data model in a dedicated .prisma file, run a generator, and get a fully-typed client. The goal is maximum developer experience with minimum friction — you shouldn't need to think about SQL.</p>
<p><strong>Drizzle</strong> takes the opposite bet. Your schema is TypeScript, your queries look like SQL, and the library stays thin on purpose. It does offer ORM-style queries too, but they feel more like a convenience layer on top of the SQL-first core than a first-class citizen. It trusts you to know what you're doing and gets out of the way. If you've ever felt ORMs were hiding too much, Drizzle is built for that instinct.</p>
<p><strong>ZenStack</strong> starts where Prisma left off. It uses a Prisma-compatible schema language and a familiar query API, but expands the scope far beyond ORM territory — built-in access control, auto-generated CRUD APIs, frontend query hooks, and a plugin system for everything else. It's schema-first like Prisma, but engineered to cover much more of the full-stack surface area. On the API side, it gives you both ends of the query spectrum: an ORM API for day-to-day simplicity, and a SQL-style query builder (powered by <a href="https://kysely.dev/" target="_blank" rel="noopener noreferrer">Kysely</a>) when you need precise control.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="2-data-modeling">2. Data Modeling<a class="hash-link" aria-label="Direct link to 2. Data Modeling" title="Direct link to 2. Data Modeling" href="https://zenstack.dev/blog/orm-2026#2-data-modeling">​</a></h2>
<p>The way you define your schema shapes everything downstream — how you think about your data and how much type safety you get out of the box.</p>
<p>Let's use a simple but realistic example: a User that has many Posts, where each post has a published status.</p>
<p><strong>Prisma</strong> uses its own schema language (PSL):</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Prisma Schema</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id    </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts</span><span class="token type-class-name"> Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">    User</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Clean and readable, even to non-developers. Prisma generates a fully-typed client from this schema, giving you strong type inference across your entire codebase.</p>
<p><strong>Drizzle</strong> keeps the schema in TypeScript — no separate file, no custom language:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Drizzle Schema</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> pgTable</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> serial</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> integer </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'drizzle-orm/pg-core'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> users </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">pgTable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'users'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">serial</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">primaryKey</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'email'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">notNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">unique</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">pgTable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'posts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">serial</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">primaryKey</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'title'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">notNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">boolean</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'published'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">integer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'author_id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">references</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> users</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>No magic, no code generation step. The schema is just TypeScript, which means your editor, your linter, and your CI pipeline all understand it natively.</p>
<p><strong>ZenStack</strong> uses a superset of Prisma's schema language, called ZModel. If you're coming from Prisma, the model definitions are identical — in fact, renaming your .prisma file to .zmodel is a valid starting point:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack Schema</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id    </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts</span><span class="token type-class-name"> Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">    User</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> published</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The <code>@@allow</code> lines are access policies — more on those in <a href="https://zenstack.dev/blog/orm-2026#4-access-control--multi-tenancy">section 4</a>. The point here is that the data modeling syntax is familiar, and since ZenStack generates the same underlying database schema as Prisma, switching costs are low.</p>
<p><strong>The takeaway</strong>: if your team prefers keeping everything in TypeScript, Drizzle wins on schema. If you want a dedicated, readable schema language, Prisma and ZenStack are effectively tied — with ZenStack gaining an edge as soon as access control or other advanced features enter the picture.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="3-querying">3. Querying<a class="hash-link" aria-label="Direct link to 3. Querying" title="Direct link to 3. Querying" href="https://zenstack.dev/blog/orm-2026#3-querying">​</a></h2>
<p>Let's fetch something realistic: all published posts for a given user, including the author's email. Same query, three tools.</p>
<p><strong>Prisma</strong> uses a nested object API that closely mirrors the shape of your schema:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Prisma Query</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    authorId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    author</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      select</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Readable, predictable, and fully typed. The result shape is inferred automatically — <code>posts[0].author.email</code> is typed as <code>string</code> with no extra work. For the vast majority of queries, this API is a pleasure to use.</p>
<p><strong>Drizzle</strong> expresses the same query in a style that will feel immediately familiar if you know SQL:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Drizzle Query</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">title</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    authorEmail</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> usersTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">innerJoin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">usersTable</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> usersTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">and</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">published</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>More verbose, but nothing is hidden. You control exactly which columns are selected, how joins are constructed, and what the result shape looks like. For developers who think in SQL, this reads naturally. For those who don't, it's a steeper mental overhead.</p>
<p><strong>ZenStack</strong> offers both styles. The Prisma-compatible API works identically:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack ORM Query</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    authorId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    author</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      select</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>And when you need more control, you can drop down to the Kysely query builder, which gives you full SQL power and even better typing than Drizzle's:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack Query Builder</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$qb</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">selectFrom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">innerJoin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'User'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'Post.id'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.title'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.email as authorEmail'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'='</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post.published'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'='</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">execute</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The ability to mix both styles in the same codebase — using the high-level API for routine queries and dropping to the query builder when precision matters — is one of ZenStack's more practical advantages.</p>
<p><strong>The takeaway</strong>: Prisma's query API is the most approachable. Drizzle's is the most SQL-faithful. ZenStack covers both ends.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="4-access-control--multi-tenancy">4. Access Control &amp; Multi-Tenancy<a class="hash-link" aria-label="Direct link to 4. Access Control &amp; Multi-Tenancy" title="Direct link to 4. Access Control &amp; Multi-Tenancy" href="https://zenstack.dev/blog/orm-2026#4-access-control--multi-tenancy">​</a></h2>
<p>Access control is where the three tools diverge most sharply. Let's use a concrete rule: users can only read their own posts, and only published posts are visible to others.</p>
<p><strong>Prisma</strong> has no built-in access control. You implement it manually, typically by adding where clauses wherever you query:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Prisma Access Control</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">OR</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> authorId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userId </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This works, but it scales poorly. Every query is a new opportunity to forget a condition. In a large codebase with many developers and AI agents, enforcing consistent access rules this way requires discipline, code reviews, and a fair amount of trust.</p>
<p><strong>Drizzle</strong> is in the same position — access control is entirely your responsibility:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Drizzle Access Control</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">or</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">published</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">postsTable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> currentUser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p><em>Worth noting: PostgreSQL's <a href="https://www.postgresql.org/docs/current/ddl-rowsecurity.html" target="_blank" rel="noopener noreferrer">Row-Level Security</a> (RLS) is a viable alternative for both tools — Drizzle even has first-class modeling support for it. The trade-offs are real though: RLS policies are harder to write and test, add operational complexity, and can surprise developers who don't expect business logic at the database layer.</em></p>
</blockquote>
<p><strong>ZenStack</strong> treats access control as a first-class concern, defined once in the schema and enforced automatically at runtime. The enforcement is handled on the application level, so it works regardless of your database choice and doesn't require any special database features. You express policies declaratively in the schema using the <code>@@allow</code> and <code>@@deny</code> attributes:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack Access Control</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">    User</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// authors can do anything to their own posts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// published posts are visible to everyone</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> published</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With these policies in place, every query through a ZenStack client automatically respects them — no manual where clauses needed:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack Access-Aware Query</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> authDb </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$setAuth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> currentUser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> authDb</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><strong>The trade-off</strong>: ZenStack's approach is more powerful but also more opinionated. You're trusting the policy engine to handle enforcement correctly, which requires learning its rules and edge cases. For simple applications, Prisma or Drizzle's manual approach may feel more transparent. For anything with real multi-tenancy requirements — SaaS products, team-scoped data, role-based access — the ZenStack model pays for itself quickly.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="5-api--framework-integration">5. API &amp; Framework Integration<a class="hash-link" aria-label="Direct link to 5. API &amp; Framework Integration" title="Direct link to 5. API &amp; Framework Integration" href="https://zenstack.dev/blog/orm-2026#5-api--framework-integration">​</a></h2>
<p>Writing database queries is only part of the story. In a full-stack TypeScript app, you also need to expose that data to your frontend — which typically means building an API layer on top of your ORM (unless you fully opt-in to server-side rendering). This is where the three tools have very different stories.</p>
<p><strong>Prisma</strong> and <strong>Drizzle</strong> are both database libraries, not API frameworks. They have no opinion on how you expose data to the outside world. You write the API yourself — whether that's REST routes in Express, route handlers in Next.js, or a GraphQL resolver layer. This is the right call for many teams: full control, no surprises, no magic. The downside is that it's boilerplate you write and maintain for every project.</p>
<p>A typical Next.js route handler with Prisma/Drizzle looks like:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">Prisma/Drizzle API Route</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// API route for fetching published posts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">posts</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Straightforward, but multiply this across every model and every operation and it adds up fast.</p>
<p><strong>ZenStack</strong> can follow the same pattern — you can use it exactly like Prisma and write your own routes. But it also offers an alternative: automatically mounting a full CRUD API directly from your schema, with access policies already enforced:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack API Route</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NextRequestHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/server/next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> db </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/lib/db'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getSessionUser </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/lib/auth'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// API handler for all CRUD operations for all models, with access control enforced</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NextRequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">getClient</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$setAuth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getSessionUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">POST</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PUT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DELETE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>That single file gives you a fully secured data API for every model in your schema — no additional route handlers needed. The access policies you defined in the schema are enforced automatically on every request.</p>
<p>On top of that, ZenStack can generate type-safe frontend hooks based on <a href="https://tanstack.com/query/" target="_blank" rel="noopener noreferrer">TanStack Query</a>, so your client code can query your backend with the same query API as your server code, with zero manual wiring:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">ZenStack Frontend Hooks</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useClientQueries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> posts </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">useFindMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The trade-off is the usual one: more automation means more to understand when something doesn't behave as expected. But for teams building standard CRUD-heavy applications — which is most SaaS products — the reduction in boilerplate is substantial.</p>
<p><strong>The takeaway</strong>: Prisma and Drizzle leave API design entirely to you, which is flexible but manual. ZenStack gives you the same escape hatch, but also offers a path where much of that layer is derived automatically from your schema and access policies.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="6-extensibility">6. Extensibility<a class="hash-link" aria-label="Direct link to 6. Extensibility" title="Direct link to 6. Extensibility" href="https://zenstack.dev/blog/orm-2026#6-extensibility">​</a></h2>
<p>No ORM covers every use case out of the box. How easy it is to extend the tool — to add behaviors, generate artifacts, or customize runtime logic — matters more than most developers realize until they hit a wall.</p>
<p><strong>Prisma</strong> has a generator-based plugin system that lets you participate in the prisma generate step. Although barely documented, this is useful for things like generating Zod schemas or ERD from your schema. For runtime extensibility, it offers <a href="https://www.prisma.io/docs/orm/prisma-client/client-extensions" target="_blank" rel="noopener noreferrer">Client Extensions</a> — a mechanism for extending <code>PrismaClient</code>'s behavior. It's a meaningful improvement but comes with many limitations that disqualify it as a full-fledged extensibility system.</p>
<p><strong>Drizzle</strong> takes a different approach to this problem by not having a plugin system at all. Extensibility in Drizzle is just TypeScript — you compose queries, wrap functions, and build abstractions the same way you would with any library. There's no framework to learn, no hook points to find. This is genuinely freeing for experienced developers, but it also means every team reinvents the same patterns independently.</p>
<p><strong>ZenStack</strong> was explicitly designed around extensibility as a core concern. Its plugin system operates at three distinct levels:</p>
<ul>
<li>Schema level — plugins can contribute new attributes, functions, and types to the ZModel language itself.</li>
<li>Generation level — plugins participate in the <code>zen generate</code> step to produce any artifact from your schema.</li>
<li>Runtime level — plugins can intercept and transform queries and results, enabling behaviors like soft deletes, field encryption, and audit logging to be implemented once and applied transparently across your entire data layer.</li>
</ul>
<p><strong>The trade-off</strong>: ZenStack's extensibility is structured. You work within its model, which has a learning curve. Drizzle's "extensibility" is just TypeScript, which will always feel more natural to developers who distrust frameworks. The right choice depends on whether you'd rather have powerful, opinionated building blocks or full freedom with no guardrails.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="7-performance">7. Performance<a class="hash-link" aria-label="Direct link to 7. Performance" title="Direct link to 7. Performance" href="https://zenstack.dev/blog/orm-2026#7-performance">​</a></h2>
<p>Performance is a genuinely complex topic for ORMs — numbers vary significantly depending on the database, query complexity, connection pooling setup, and whether you're measuring cold starts or steady-state throughput. No single benchmark tells the full story, and synthetic tests rarely match real workload distributions.</p>
<p>That said, all three tools publish their own benchmark results, which are worth reviewing in context:</p>
<ul>
<li><strong>Prisma</strong>: <a href="https://benchmarks.prisma.io/" target="_blank" rel="noopener noreferrer">benchmarks.prisma.io</a></li>
<li><strong>Drizzle</strong>: <a href="https://orm.drizzle.team/benchmarks" target="_blank" rel="noopener noreferrer">orm.drizzle.team/benchmarks</a></li>
<li><strong>ZenStack</strong>: <a href="https://zenstack.dev/docs/orm/benchmark" target="_blank" rel="noopener noreferrer">zenstack.dev/docs/orm/benchmark</a></li>
</ul>
<p>If you're optimizing for raw throughput at scale, the official numbers are the right place to start — but for most applications, the differences likely won't be the deciding factor.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="8-ecosystem--maturity">8. Ecosystem &amp; Maturity<a class="hash-link" aria-label="Direct link to 8. Ecosystem &amp; Maturity" title="Direct link to 8. Ecosystem &amp; Maturity" href="https://zenstack.dev/blog/orm-2026#8-ecosystem--maturity">​</a></h2>
<p>Features and API design matter, but so does the world around a tool — community size, documentation quality, available integrations, and how confident you can be putting it in production.</p>
<p><em>Ecosystem &amp; maturity comparison — as of early 2026</em></p>
<table><thead><tr><th></th><th>Prisma</th><th>Drizzle</th><th>ZenStack</th></tr></thead><tbody><tr><td>GitHub stars</td><td>~45k</td><td>~32k</td><td>~3k</td></tr><tr><td>Production readiness</td><td>GA</td><td>GA</td><td>GA (v3 is recent)</td></tr><tr><td>Documentation</td><td>Excellent</td><td>Good</td><td>Good</td></tr><tr><td>GUI tooling</td><td><a href="https://www.prisma.io/studio" target="_blank" rel="noopener noreferrer">Prisma Studio</a></td><td><a href="https://orm.drizzle.team/drizzle-studio/overview" target="_blank" rel="noopener noreferrer">Drizzle Studio</a></td><td><a href="https://zenstack.dev/docs/studio">ZenStack Studio</a></td></tr><tr><td>Database support</td><td>PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB</td><td>PostgreSQL, MySQL, SQLite, SQL Server</td><td>PostgreSQL, MySQL, SQLite</td></tr><tr><td>Serverless / edge</td><td>✓ (since v7)</td><td>✓</td><td>✓</td></tr><tr><td>Community size</td><td>Large</td><td>Growing fast</td><td>Smaller but active</td></tr></tbody></table>
<p>A few things worth expanding on:</p>
<ul>
<li><strong>Prisma</strong> is the clear ecosystem leader. Years of adoption means more Stack Overflow answers, more community plugins, more starter kits, and more teammates who already know it. Documentation is among the best in the TypeScript ecosystem. The main concern is strategic: Prisma the company has increasingly shifted focus toward its hosted products (Prisma Postgres, Prisma Accelerate), and ORM innovation has slowed as a result.</li>
<li><strong>Drizzle</strong> has grown remarkably fast for a newer tool — evolving from a lightweight alternative into a serious contender, production adoption at scale, and performance numbers that matter for serverless and edge deployments. The community is active and vocal, and documentation has improved significantly.</li>
<li><strong>ZenStack</strong> is the youngest of the three by community size, and v3 — while stable — is a relatively recent release. The documentation is solid, and the Discord community is active and responsive. The plugin ecosystem is early but growing quickly; community-built plugins for real-time, caching, and tRPC already exist. Given its schema language and query API are both Prisma-compatible, it offers an easy migration path for Prisma users who desire a more lightweight, extensible, and feature-rich alternative.</li>
</ul>
<blockquote>
<p><em>One thing worth watching: Prisma has an experimental <a href="https://github.com/prisma/prisma-next" target="_blank" rel="noopener noreferrer">prisma-next</a> project in development — a full TypeScript rewrite with a proper extension system, suggesting the team is aware of the extensibility gap. The ORM space is moving fast.</em></p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion-how-to-choose">Conclusion: How to Choose<a class="hash-link" aria-label="Direct link to Conclusion: How to Choose" title="Direct link to Conclusion: How to Choose" href="https://zenstack.dev/blog/orm-2026#conclusion-how-to-choose">​</a></h2>
<p>No single tool wins across the board. The right choice depends on what your project actually needs. Here's a practical decision framework:</p>
<p><strong>Choose Prisma if:</strong></p>
<ul>
<li>You want the most mature ecosystem with the largest community</li>
<li>Your team includes developers who aren't SQL-fluent and benefit from a high-abstraction query API</li>
<li>You're already on Prisma and the missing features aren't blocking you — switching has real costs</li>
<li>You want battle-tested tooling and don't care much about bundle size</li>
</ul>
<p><strong>Choose Drizzle if:</strong></p>
<ul>
<li>You're comfortable with SQL and want your queries to reflect that</li>
<li>You're deploying to serverless or edge runtimes where bundle size and cold start times matter</li>
<li>You want maximum flexibility with no framework opinions imposed on you</li>
<li>Your team prefers TypeScript-native everything, including schema definition</li>
</ul>
<p><strong>Choose ZenStack if:</strong></p>
<ul>
<li>You're building a multi-tenant application, a SaaS product, or anything where access control is a first-class concern</li>
<li>You want to reduce boilerplate across the full stack — from database to API to frontend hooks</li>
<li>You're coming from Prisma and want a low-friction migration path with significantly more capability</li>
<li>Your project would benefit from a schema that serves as a single source of truth for data, security, and API shape</li>
</ul>
<p>Regarding team size: for solo developers or very small teams, any of the three works fine. As teams grow, ZenStack's declarative access control becomes increasingly valuable — the alternative is trusting every developer (or agent) to remember every where clause on every query, forever.</p>
<p>Greenfield vs. existing projects: if you're starting fresh, all three are equally viable starting points. If you're migrating an existing Prisma codebase, ZenStack offers a genuine <a href="https://zenstack.dev/docs/migrate-prisma">migration path</a> from Prisma without requiring a major rewrite.</p>
<p>The TypeScript ORM space in 2026 is healthier than it's ever been — you have real choices with real trade-offs, not just one obvious default. Whichever you pick, the official docs are the best starting point: <a href="https://www.prisma.io/docs" target="_blank" rel="noopener noreferrer">Prisma</a>, <a href="https://orm.drizzle.team/docs/overview" target="_blank" rel="noopener noreferrer">Drizzle</a>, <a href="https://zenstack.dev/docs" target="_blank" rel="noopener noreferrer">ZenStack</a>.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>orm</category>
            <category>typescript</category>
            <category>prisma</category>
            <category>drizzle</category>
            <category>zenstack</category>
        </item>
        <item>
            <title><![CDATA[Your Stack Choice Is Coding Agent's Safety Net]]></title>
            <link>https://zenstack.dev/blog/stack-safety-net</link>
            <guid>https://zenstack.dev/blog/stack-safety-net</guid>
            <pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In the age of AI-assisted coding, your tech stack is no longer just a developer preference — it's a guardrail that determines how safely and effectively coding agents can operate.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-93302149f67f73a00cd99bcbf332426b.png" width="1536" height="1024" class="img_XvmG"></p>
<p>We're in the "vibe coding" era. AI coding agents — Cursor, Claude Code, Copilot, Devin, and their growing kin — are no longer writing toy snippets. They're scaffolding entire features, refactoring modules, and wiring up API endpoints in seconds. The promise is intoxicating: describe what you want, get working code.</p>
<p>The reality is more nuanced. These agents are prolific, but not infallible. They hallucinate API signatures. They skip edge cases. They introduce subtle bugs that compile cleanly and pass a first glance. And crucially, they do all of this <strong>fast</strong> — far faster than any human code review can keep up with.</p>
<p>So here's the question that matters more than ever: <em>what catches the mistakes an agent makes?</em></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="we-need-a-new-way-to-evaluate-a-stack">We Need a New Way to Evaluate a Stack<a class="hash-link" aria-label="Direct link to We Need a New Way to Evaluate a Stack" title="Direct link to We Need a New Way to Evaluate a Stack" href="https://zenstack.dev/blog/stack-safety-net#we-need-a-new-way-to-evaluate-a-stack">​</a></h2>
<p>For years, we've chosen tech stacks based on developer experience, ecosystem maturity, performance characteristics, and hiring pool. These criteria still matter. But a new dimension has entered the picture: <strong>how well does the stack constrain and verify AI-generated code?</strong></p>
<p>Think about it this way. When a human writes code, they bring context, intuition, and institutional knowledge. They remember the auth check that needs to go on every endpoint. They know the subtle business rule about tenant isolation. An AI agent has none of that ambient awareness — it operates on whatever context it's given, and it fills in the gaps with statistical plausibility.</p>
<p>This means a tech stack is no longer just a productivity tool for humans. It's a <strong>safety net for agents</strong>. The right stack catches agent mistakes before they reach production. The wrong stack lets them sail through silently.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="what-makes-a-stack-agent-friendly">What Makes a Stack "Agent-Friendly"?<a class="hash-link" aria-label="Direct link to What Makes a Stack &quot;Agent-Friendly&quot;?" title="Direct link to What Makes a Stack &quot;Agent-Friendly&quot;?" href="https://zenstack.dev/blog/stack-safety-net#what-makes-a-stack-agent-friendly">​</a></h2>
<p>There are three qualities that make a stack effective at catching agent errors:</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-strong-type-system--compile-time-guardrails">1. Strong Type System = Compile-Time Guardrails<a class="hash-link" aria-label="Direct link to 1. Strong Type System = Compile-Time Guardrails" title="Direct link to 1. Strong Type System = Compile-Time Guardrails" href="https://zenstack.dev/blog/stack-safety-net#1-strong-type-system--compile-time-guardrails">​</a></h3>
<p>This is the most obvious one, but it's worth stating clearly. A strong type system acts as a first line of defense against the kinds of errors agents commonly make.</p>
<p>Consider TypeScript vs. plain JavaScript. When an agent adds a new field to a data model, TypeScript propagates that change across the codebase — every function that touches that model lights up with type errors until it's updated. In plain JS, the agent might update the model and the handler it's currently looking at, while a dozen other consumers silently break.</p>
<p>The same principle applies to data access. A typed ORM like Prisma or Drizzle makes it structurally impossible to write queries that reference non-existent fields or use wrong types. Raw SQL strings? The agent can generate whatever it wants, and you won't know it's wrong until runtime — or worse, until a user hits the broken path in production.</p>
<p><strong>The takeaway: types turn runtime surprises into compile-time errors. For human developers, that's nice. For AI agents generating code at speed, it's essential.</strong></p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-declarative-rules-over-imperative-logic">2. Declarative Rules Over Imperative Logic<a class="hash-link" aria-label="Direct link to 2. Declarative Rules Over Imperative Logic" title="Direct link to 2. Declarative Rules Over Imperative Logic" href="https://zenstack.dev/blog/stack-safety-net#2-declarative-rules-over-imperative-logic">​</a></h3>
<p>Here's where things get interesting. Consider how most web apps handle authorization. The typical pattern looks like this:</p>
<div class="language-typescript codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-typescript codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// In route handler A</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">role </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> resource</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ownerId </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ForbiddenError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// In route handler B (slightly different check)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">role </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> resource</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tenantId </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ForbiddenError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// In route handler C (oops, forgot the check entirely)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is imperative authorization — the rules are expressed as code, scattered across dozens of files, with subtle variations that may or may not be intentional. Now imagine asking an AI agent to add a new endpoint. It needs to somehow infer the correct authorization pattern from examples, and apply it correctly. Sometimes it will. Sometimes it won't. And when it doesn't, you have a security vulnerability.</p>
<p>Now contrast this with a declarative approach:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Resource </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">role </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> tenantId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">role </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The rules live in one place. They're structural, not behavioral. An agent literally cannot create an endpoint that bypasses them, because the access control is enforced at the data layer, not at the route level. <strong>The more business logic lives in declarations, the less room there is for agents to introduce inconsistencies.</strong></p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3-single-source-of-truth-for-data-shape-and-rules">3. Single Source of Truth for Data Shape and Rules<a class="hash-link" aria-label="Direct link to 3. Single Source of Truth for Data Shape and Rules" title="Direct link to 3. Single Source of Truth for Data Shape and Rules" href="https://zenstack.dev/blog/stack-safety-net#3-single-source-of-truth-for-data-shape-and-rules">​</a></h3>
<p>AI agents perform best when context is concentrated, not scattered. This is a fundamental property of how large language models work — they reason over a context window, and the more relevant information fits within that window, the better their output.</p>
<p>Schema-first approaches shine here. When your data model, validation rules, and access policies all live in a single schema file, an agent can read that one file and understand the complete picture. Compare this to a codebase where the data models are in a bunch of migration files, validations are in a middleware folder, access rules are in a service layer, and business constraints are buried in utility functions. Even a skilled human developer struggles to hold all of that in their head. For an agent, it's nearly impossible.</p>
<p><strong>The best stack for the AI era is one where the "spec" of your data layer fits in one place.</strong></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="when-the-safety-net-is-missing">When the Safety Net Is Missing<a class="hash-link" aria-label="Direct link to When the Safety Net Is Missing" title="Direct link to When the Safety Net Is Missing" href="https://zenstack.dev/blog/stack-safety-net#when-the-safety-net-is-missing">​</a></h2>
<p>To make this concrete, here are failure modes we've seen (or narrowly avoided) with AI-generated code:</p>
<p><strong>The forgotten auth check.</strong> An agent writes a new API endpoint for fetching user invoices. It follows the pattern of existing endpoints — proper input validation, clean error handling, nice response formatting. But it doesn't add an authorization check, because the auth logic isn't in the ORM layer or the route middleware — it's manually applied per handler, and the agent didn't infer the pattern from context. Result: any authenticated user can read any other user's invoices.</p>
<p><strong>The inconsistent validation.</strong> Your app validates <code>email</code> format in a Zod schema inside the signup handler, but the profile update handler has its own inline regex check, and the admin user-creation endpoint has no email validation at all. An agent is asked to add a new "invite user" endpoint. It writes one — with a slightly different email regex copied from the profile handler. Now you have four endpoints, three different validation rules, and one with none. The inconsistency is invisible at compile time and slips right through tests that only check the happy path.</p>
<p><strong>The wrong tenant scope.</strong> An agent is asked to add a data export feature. It copy-pastes a query pattern from another feature, but the original query was for a single-tenant context. The new feature runs in a multi-tenant context, and the agent doesn't apply the tenant filter. Result: data leakage across tenants.</p>
<p>The common thread in all of these: <strong>the failures happen when rules are implicit and scattered.</strong> No single file told the agent "every query must be tenant-scoped" or "every endpoint must check ownership." The rules existed only as patterns in the codebase, and patterns are exactly what LLMs approximate — imperfectly.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="how-zenstack-acts-as-the-safety-net">How ZenStack Acts as the Safety Net<a class="hash-link" aria-label="Direct link to How ZenStack Acts as the Safety Net" title="Direct link to How ZenStack Acts as the Safety Net" href="https://zenstack.dev/blog/stack-safety-net#how-zenstack-acts-as-the-safety-net">​</a></h2>
<p><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> is a TypeScript toolkit centered around a (Prisma-like) schema language called ZModel. In ZModel, you define your data model, access control rules, and validation constraints in a single file:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Invoice </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  amount    </span><span class="token entity" style="color:#36acaa">Float</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@gt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  owner</span><span class="token type-class-name">     User</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId   </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tenant</span><span class="token type-class-name">    Tenant</span><span class="token plain">   </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tenantId  </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Access rules: declarative, co-located with the model</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// no anonymous access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// can create for self in the context of a tenant</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> tenantId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// can read if in the same tenant</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tenantId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// can update or delete if owner</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update,delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>From this single schema, ZenStack generates a type-safe, access-controlled database client. Here's what that means for AI agents:</p>
<ul>
<li>
<p><strong>Authorization is structural.</strong> An agent can't "forget" to add an auth check because the check happens automatically at the data layer. Every query through ZenStack's enhanced client is filtered by the rules in the schema. The agent's job is just to write the query — the safety net handles the rest.</p>
</li>
<li>
<p><strong>The context is concentrated.</strong> An agent can read the ZModel file and understand the complete data model, relationships, access rules, and validation constraints. No hunting across files to piece together the full picture.</p>
</li>
<li>
<p><strong>Type safety propagates.</strong> The generated client is fully typed. If an agent writes a query that references a non-existent field or uses an invalid filter, TypeScript catches it before the code runs.</p>
</li>
<li>
<p><strong>Less code to generate, less to get wrong.</strong> ZenStack and its ecosystem can automatically derive CRUD APIs, TanStack Query hooks, Zod schemas, tRPC routers, OpenAPI specs, and more. Every auto-generated artifact is one less thing an agent needs to hand-write — and one less place for bugs to hide.</p>
</li>
</ul>
<p>Going back to the failure modes above:</p>
<ul>
<li><strong>Forgotten auth check?</strong> Can't happen — ZenStack's enhanced client enforces rules automatically.</li>
<li><strong>Inconsistent validation?</strong> Unlikely — validation rules live in the schema alongside the model, so every endpoint shares the same constraints automatically.</li>
<li><strong>Wrong tenant scope?</strong> The <code>tenantId == auth().tenantId</code> rule in the schema ensures every query is scoped correctly, regardless of which endpoint makes the query.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="shrink-the-blast-radius">Shrink the Blast Radius<a class="hash-link" aria-label="Direct link to Shrink the Blast Radius" title="Direct link to Shrink the Blast Radius" href="https://zenstack.dev/blog/stack-safety-net#shrink-the-blast-radius">​</a></h2>
<p>The broader principle here isn't specific to ZenStack. It's this: <strong>the best stacks for the AI era minimize what can go wrong when code is generated at speed.</strong></p>
<p>Think of type systems as guardrails on a highway. They don't slow you down — you can still drive at full speed. But they keep you from flying off a cliff when you drift. Declarative schemas, enforced access control, and auto-generated boilerplate are the same kind of guardrails, applied to the data and security layer — which is exactly where mistakes hurt the most.</p>
<p>The stacks that will thrive in the agent era share a common philosophy: <strong>make the right thing automatic, and the wrong thing impossible.</strong> The more of your application's correctness invariants that are expressed as declarations rather than scattered imperative code, the safer you are — whether the code is written by a human, an agent, or some combination of both.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="choose-stacks-that-protect-you-from-speed">Choose Stacks That Protect You From Speed<a class="hash-link" aria-label="Direct link to Choose Stacks That Protect You From Speed" title="Direct link to Choose Stacks That Protect You From Speed" href="https://zenstack.dev/blog/stack-safety-net#choose-stacks-that-protect-you-from-speed">​</a></h2>
<p>AI agents are only getting faster and more autonomous. The question is no longer whether to use them — it's whether your stack can keep up safely. A good stack isn't just pleasant to work with. It absorbs the speed and imprecision of AI-generated code. It turns out that what's good for human developers is even better for AI agents: clear, concentrated, enforceable rules.</p>
<p>If you're evaluating your stack for the agent era, ask yourself: <strong>when an AI writes code against my data layer, what stops it from making a catastrophic mistake?</strong> If the answer is "code review" or "hoping the tests catch it," it might be time to add a stronger safety net.</p>
<p><a href="https://zenstack.dev/docs/" target="_blank" rel="noopener noreferrer">Get started with ZenStack</a> and see what a schema-first, safety-net-first stack looks like in practice.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>ai</category>
            <category>agent</category>
            <category>llm</category>
            <category>zenstack</category>
        </item>
        <item>
            <title><![CDATA[ZenStack V3: The Perfect Prisma ORM Alternative]]></title>
            <link>https://zenstack.dev/blog/prisma-alternative</link>
            <guid>https://zenstack.dev/blog/prisma-alternative</guid>
            <pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Why fighting Prisma's limitations when you can have a better choice?]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-418bc50e4b400b3d03fd64e9fcdb5397.png" width="2794" height="1238" class="img_XvmG"></p>
<p><a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a> won the hearts of many TypeScript developers with its excellent developer experience - the elegant schema language, the intuitive query API, and the unmatched type-safety. However, as time went by, its focus shifted, and innovation in the ORM space slowed down considerably. Many successful OSS projects indeed struggle to meet the ever-growing demands, but you'll be surprised to find that many seemingly fundamental features that have been requested for years are still missing today, such as:</p>
<ul>
<li><a href="https://github.com/prisma/prisma/issues/3219" target="_blank" rel="noopener noreferrer">Define type of content of Json field #3219</a></li>
<li><a href="https://github.com/prisma/prisma/issues/1644" target="_blank" rel="noopener noreferrer">Support for Polymorphic Associations #1644</a></li>
<li><a href="https://github.com/prisma/prisma/issues/3398" target="_blank" rel="noopener noreferrer">Soft deletes (e.g. deleted_at) #3398</a></li>
</ul>
<p>As a new challenger in this space, <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> aspires to be the spiritual successor to Prisma, but with a light-weighted architecture, a richer feature set, well-thought-out extensibility, and an easy-to-contribute codebase. Furthermore, its being essentially compatible with Prisma means you can have a smooth transition from existing projects.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-bit-of-history">A bit of history<a class="hash-link" aria-label="Direct link to A bit of history" title="Direct link to A bit of history" href="https://zenstack.dev/blog/prisma-alternative#a-bit-of-history">​</a></h2>
<p>ZenStack began its journey as a power pack for Prisma ORM in late 2022. It extended Prisma's schema language with additional attributes and functions, and enhanced <code>PrismaClient</code> at runtime with extra features. The most notable enhancement is the introduction of access policies, which allow you to declaratively define fine-grained access rules in the schema had have them transparently enforced at runtime.</p>
<p>As we went deeper along the path with v1 and v2, we felt increasingly constrained by Prisma's intrinsic limitations. We decided to make a bold change in v3: build our own ORM engine (on top of the awesome <a href="https://kysely.dev/" target="_blank" rel="noopener noreferrer">Kysely</a>) and migrate away from Prisma. To ensure an easy migration path, we've made several essential compatibility commitments:</p>
<ul>
<li>The schema language remains compatible with (and in fact a superset of) Prisma Schema Language (PSL).</li>
<li>The resulting database schema remains unchanged, so no data migration is needed.</li>
<li>The query API stays compatible with PrismaClient.</li>
<li>Existing migration records continue to work without changes.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="dual-api-from-a-single-schema">Dual API from a single schema<a class="hash-link" aria-label="Direct link to Dual API from a single schema" title="Direct link to Dual API from a single schema" href="https://zenstack.dev/blog/prisma-alternative#dual-api-from-a-single-schema">​</a></h2>
<p>PrismaClient's API is pleasant to use, but when you go beyond simple queries, you'll have to resort to raw SQL queries. Prisma introduced the <a href="https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql" target="_blank" rel="noopener noreferrer">TypedSQL</a> feature to mitigate this problem. Unfortunately, it only solved half of the problem (having the query results typed), but you still have to write SQL, which is quite a drop in developer experience.</p>
<p>ZenStack v3, thanks to the power of Kysely, offers a dual API design. You can continue to use the elegant high-level ORM queries like:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> users </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> age</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> gt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">18</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> posts</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Or, when your needs outgrow its power, you can seamlessly switch to Kysely's type-safe, fluent query builder API:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> users </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$qb</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">selectFrom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'User'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'age'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'&gt;'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">18</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">leftJoin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'User.*'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.title'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">execute</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>For most applications, you'll never need to write a single line of SQL anymore.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="battery-included">Battery included<a class="hash-link" aria-label="Direct link to Battery included" title="Direct link to Battery included" href="https://zenstack.dev/blog/prisma-alternative#battery-included">​</a></h2>
<p>ZenStack aims to be a battery-included ORM and solve many common data modeling/query problems in a coherent package. Here are just a few examples of how far it's come in achieving this goal.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="built-in-access-control">Built-in access control<a class="hash-link" aria-label="Direct link to Built-in access control" title="Direct link to Built-in access control" href="https://zenstack.dev/blog/prisma-alternative#built-in-access-control">​</a></h3>
<p>Every serious application needs non-trivial authorization. Wouldn't it be great if you could consolidate all access rules in the data model and never need to worry about them at query time? Here you go:</p>
<p><strong>Schema:</strong></p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">    User</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// deny anonymous access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// owner has full access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> published</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// published posts are publicly readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><strong>Query:</strong></p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">handleRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// get the validated current user from your auth system</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getCurrentUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// create a user-bound ORM client</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> userDb </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$setAuth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// query with access policies automatically enforced</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> posts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> userDb</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="strongly-typed-json">Strongly typed JSON<a class="hash-link" aria-label="Direct link to Strongly typed JSON" title="Direct link to Strongly typed JSON" href="https://zenstack.dev/blog/prisma-alternative#strongly-typed-json">​</a></h3>
<p>JSON columns are becoming increasingly popular in relational databases. Getting them typed is straightforward.</p>
<p><strong>Schema</strong>:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id      </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  profile</span><span class="token type-class-name"> Profile</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Profile </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  age </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  bio </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><strong>Query</strong>:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findFirstOrThrow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">age</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// strongly typed</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="polymorphic-models">Polymorphic models<a class="hash-link" aria-label="Direct link to Polymorphic models" title="Direct link to Polymorphic models" href="https://zenstack.dev/blog/prisma-alternative#polymorphic-models">​</a></h3>
<p>Ever felt the need to model an inheritance hierarchy in the database? Polymorphic models come to the rescue.</p>
<p><strong>Schema</strong>:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Content </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain">      </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// marks this model to be a polymorphic base with its</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// concrete type designated by the "type" field</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@delegate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">type</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">Content</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  content </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Image </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">Content</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  data </span><span class="token entity" style="color:#36acaa">Bytes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><strong>Query</strong>:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// a query with base automatically includes sub-model fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> content </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findFirstOrThrow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// the returned type is a discriminated union</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">type </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// typed narrowed to `Post`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">type </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Image'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// typed narrowed to `Image`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<hr>
<p>These are just some of the existing features. More cool stuff like soft deletes, audit trails, etc., will be added in the future.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="extensible-from-the-ground-up">Extensible from the ground up<a class="hash-link" aria-label="Direct link to Extensible from the ground up" title="Direct link to Extensible from the ground up" href="https://zenstack.dev/blog/prisma-alternative#extensible-from-the-ground-up">​</a></h2>
<p>ZenStack ORM comprises three main pillars - the schema language, the CLI, and the ORM runtime. All three are designed with extensibility in mind.</p>
<ul>
<li>
<p>The schema language allows you to add custom attributes and functions to extend its semantics freely. E.g., you can add an <code>@encrypted</code> attribute to mark fields that need encryption.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">attribute</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@encrypted</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id       </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    email    </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@unique</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@encrypted</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>The ORM runtime allows you to add plugins that can intercept queries at different levels and modify their payload or entire behavior. For the example above, you can create a plugin that recognizes the <code>@encrypted</code> attribute and transparently encrypts/decrypts field values during writes/reads.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dbWithEncryption </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">EncryptionPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    algorithm</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'AES-256-CBC'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>The CLI allows you to add generators that emit custom artifacts based on the schema. Think of generating an ERD diagram, or a GraphQL schema.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">plugin</span><span class="token plain"> erd </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./plugins/erd-generator'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  output </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./erd-diagram.md"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ul>
<p>In fact, the access control feature mentioned earlier is entirely implemented with these extension points as a plugin package. The potential is limitless.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="simpler-smaller-footprint">Simpler, smaller footprint<a class="hash-link" aria-label="Direct link to Simpler, smaller footprint" title="Direct link to Simpler, smaller footprint" href="https://zenstack.dev/blog/prisma-alternative#simpler-smaller-footprint">​</a></h2>
<p>ZenStack is a monorepo, 100% TypeScript project - no native binaries, no WASM modules. It keeps things lean and reduces deployment footprint. As a quick comparison, for a minimal project that uses Postgres database, the "node_modules" size difference (with <code>npm install --omit=dev</code>) is quite significant:</p>
<table><thead><tr><th>ORM</th><th>"node_modules" Size</th></tr></thead><tbody><tr><td>Prisma 7</td><td>224 MB</td></tr><tr><td>ZenStack V3</td><td>33 MB</td></tr></tbody></table>
<p>A simpler code base also makes it easier for the community to navigate and contribute.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="beyond-orm">Beyond ORM<a class="hash-link" aria-label="Direct link to Beyond ORM" title="Direct link to Beyond ORM" href="https://zenstack.dev/blog/prisma-alternative#beyond-orm">​</a></h2>
<p>A feature-rich ORM can enable some very interesting new use cases. For example, since the ORM is equipped with access control, it can be directly mapped to a service that offers a full-fledged data query API without writing any code. You effectively get a self-hosted Backend-as-a-Service, but without any vendor lock-in. Check out the <a href="https://zenstack.dev/docs/service">Query-as-a-Service</a> documentation if you're interested.</p>
<p>Furthermore, <a href="https://zenstack.dev/docs/service/client-sdk/tanstack-query/">frontend query hooks</a> (based on <a href="https://tanstack.com/query" target="_blank" rel="noopener noreferrer">TanStack Query</a>) can be automatically derived, and they work seamlessly with the backend service.</p>
<p>All summed up, the project's goal is to be the data layer of modern full-stack applications. Kill boilerplate code, eliminate redundancy, and let your data model drive as many aspects as possible.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/prisma-alternative#conclusion">​</a></h2>
<p>ZenStack v3 is currently in Beta, and a production-ready version will land soon. If you're interested in trying out migrating an existing Prisma project, you can find a more thorough guide <a href="https://zenstack.dev/docs/migrate-prisma">here</a>. Make sure to join us in <a href="https://discord.gg/Ykhr738dUe" target="_blank" rel="noopener noreferrer">Discord</a>, and we'd love to hear your feedback!</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>prisma</category>
            <category>orm</category>
        </item>
        <item>
            <title><![CDATA[Turning Your Database Into an MCP Server With Auth]]></title>
            <link>https://zenstack.dev/blog/database-to-mcp</link>
            <guid>https://zenstack.dev/blog/database-to-mcp</guid>
            <pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Benefits of using the Model Context Protocol (MCP) with OAuth and how to implement it using ZenStack and MCP SDK.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-0934114a995e59fa7f2574bcf1d6bb30.png" width="1536" height="1024" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="mcp-is-trending-in-ai--but">MCP is trending in AI,  But…<a class="hash-link" aria-label="Direct link to MCP is trending in AI,  But…" title="Direct link to MCP is trending in AI,  But…" href="https://zenstack.dev/blog/database-to-mcp#mcp-is-trending-in-ai--but">​</a></h2>
<p>MCP(Model Context Protocol) is undoubtedly one of the most exciting trends in AI now. Not only is everyone talking about it, but everyone is also rushing to develop their version to secure a seat at the table of the AI era. Don't feel so?  Guess how many MCP servers are listed in <a href="https://www.pulsemcp.com/servers" target="_blank" rel="noopener noreferrer">MCP Server Directory</a> ?</p>
<p>The number is 4,684 now, and remember, MCP is just a six-month-old baby.   However, have you noticed the one and only filter on the left:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/2dd0a708-16fb-48af-bca0-1089abd2e63b" alt="mcp-filter" class="img_XvmG"></p>
<p>Guess how many remain after applying this filter? Only 59—approximately 1% of the total. For those unfamiliar with this filter, I'll provide a brief explanation that you can skip if you already understand it.</p>
<p>MCP was initially conceived primarily as a protocol for local execution, where AI models could interact with local tools and data sources running on the same machine, like the example MCP FileSystem and Fetch listed in the official doc. It means you must install and run the MCP server on your local machine, regardless of whether the underlying transport is stdio or HTTP SSE. This design choice made sense in the early days, as it simplified security concerns and reduced latency. The protocol's simplicity made it perfect for developers to experiment with AI tool integration on their personal machines. However, as MCP gained traction and the AI ecosystem evolved, the need for secure remote execution became increasingly apparent:</p>
<div align="center"><p><a href="https://x.com/kentcdodds/status/1907218594624372868" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/56c26bb2-bf7f-423e-9a2a-7416f09298a7" alt="Kent-Tweet" class="img_XvmG"></a></p></div>
<p>With a remote MCP server, organizations expose their data and services to AI models over the internet,  so the most important thing is Auth (authentication and authorization). Initially, this was addressed by asking users to generate an API key within the product and then configure it in the MCP settings. Here is an example from the Neon MCP server:</p>
<div class="language-json codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-json codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"mcpServers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"neon"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"command"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npx"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"args"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"-y"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"@neondatabase/mcp-server-neon"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"start"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"&lt;YOUR_NEON_API_KEY&gt;"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Developers are all familiar with this process. But as more and more SaaS products are providing the MCP server due to fear of death(search ‘SaaS is dead’ if you don't get it 😁), that's definitely not a good user experience for the non-developers. Moreover, users need to manually update the NPM package from time to time to stay current.</p>
<p>Kent raises the same concern in his tweet:</p>
<blockquote>
<p>All the examples I see so far have the user generate a token and then put that token in the MCP configuration. Is that the best we have right now? I was hoping to find an example of an MCP client that supported an OAuth flow with a hosted MCP server.</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-advent-of-authorization-for-mcp">The Advent of Authorization for MCP<a class="hash-link" aria-label="Direct link to The Advent of Authorization for MCP" title="Direct link to The Advent of Authorization for MCP" href="https://zenstack.dev/blog/database-to-mcp#the-advent-of-authorization-for-mcp">​</a></h2>
<p>The MCP is young but is growing very fast. The OAuth support was introduced in March 2025, around the time he was 3 months old. Here are the Authorization flow steps in the <a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization" target="_blank" rel="noopener noreferrer">official specification</a>:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/6c8857ab-4436-4fc9-b264-10409e61a2ab" alt="MCP-Auth-Flow" class="img_XvmG"></p>
<p>The specification comes with SDK support for both client and server, enabling more MCP servers to adopt and leverage it.   Even some existing servers that rely on API keys are beginning to embrace the new modern solution. For example, the Neon MCP server mentioned above adds support for it, too.</p>
<div class="language-json codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-json codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"mcpServers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"Neon"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"command"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npx"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"args"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"-y"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mcp-remote"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://mcp.neon.tech/sse"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<aside><p>💡The <code>mcp-remote</code> is for clients that don’t support OAuth. It's unnecessary for clients like Cursor that have supported it.</p></aside>
<p>After adding the above MCP configuration, the MCP client will automatically open the browser to go through the standard OAuth process without needing the API key anymore:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/c3eb58f9-db43-4376-bf80-af5a342dfc3d" alt="Neon-OAuth" class="img_XvmG"></p>
<p>With this modern MCP server, you can stop worrying about the PM or CEO showing up at your desk asking for help connecting to the MCP server of some SaaS product your company uses. Trust me, it happens. 😂</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="mcp-is-a-good-enhancement-for-saas">MCP Is a Good Enhancement for SaaS<a class="hash-link" aria-label="Direct link to MCP Is a Good Enhancement for SaaS" title="Direct link to MCP Is a Good Enhancement for SaaS" href="https://zenstack.dev/blog/database-to-mcp#mcp-is-a-good-enhancement-for-saas">​</a></h2>
<p>With the seamless experience of OAuth support, many companies are eager to provide an MCP server to enhance their existing product or service, especially within the SaaS landscape. Balancing flexibility and simplicity is always a core challenge for SaaS  - too many buttons overwhelm users, too few constrain power users.   For instance, I always love the clean and efficient UX/UI design of Trello:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/d9ec4451-6c76-484c-858f-8f8a02e5ddbf" alt="Trello" class="img_XvmG"></p>
<p>However, it pricks me every time for certain routine jobs.</p>
<ul>
<li>How many cards were done last week?</li>
<li>Who has the most incomplete cards?</li>
<li>Which list has the most incomplete cards?</li>
</ul>
<p>Trello doesn’t provide a direct way to show the answer; you have to do the math manually.   This is where the MCP-LLM duo could provide a great solution to let user use natural language to get their job done.    I think that’s the actual point that Microsoft CEO Satya Nadella was trying to convey in his presumed “SaaS is dead” interview.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="two-approaches-to-building-an-mcp-server">Two Approaches to Building an MCP Server<a class="hash-link" aria-label="Direct link to Two Approaches to Building an MCP Server" title="Direct link to Two Approaches to Building an MCP Server" href="https://zenstack.dev/blog/database-to-mcp#two-approaches-to-building-an-mcp-server">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-relying-on-the-existing-api">1. Relying on the Existing API<a class="hash-link" aria-label="Direct link to 1. Relying on the Existing API" title="Direct link to 1. Relying on the Existing API" href="https://zenstack.dev/blog/database-to-mcp#1-relying-on-the-existing-api">​</a></h3>
<p>This seems like the obvious choice. However,  since those APIs were most likely designed a long time before the LLM was born, they might not be suitable for the LLM to consume.  One thing is that the semantics of the parameters and return data might not be self-explanatory enough for the LLM to understand; The other thing is that the API might not be flexible enough to provide the functionality to support what the user wants to achieve.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-start-from-scratch">2. Start from Scratch<a class="hash-link" aria-label="Direct link to 2. Start from Scratch" title="Direct link to 2. Start from Scratch" href="https://zenstack.dev/blog/database-to-mcp#2-start-from-scratch">​</a></h3>
<p>For those considering this approach, the biggest concern is probably time.   Don’t worry,  we never really build from scratch, do we?  The MCP is evolving fast, and so is the whole ecosystem. Let me show you the modern stack that could dramatically reduce the code you need to write to speed it up.</p>
<ul>
<li>
<p><a href="https://github.com/modelcontextprotocol/typescript-sdk" target="_blank" rel="noopener noreferrer">MCP TypeScript SDK</a></p>
<p>It fully implements the MCP specification, including Authorization. It provides a strong abstraction, freeing you from wrestling with low-level protocol implementation so you can focus on the business logic.</p>
</li>
<li>
<p><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a></p>
<p>ZenStack is a schema-first TypeScript toolkit on top of Prisma ORM.  The core part is a ZModel DSL that unifies data modeling and access control.  Here is an example of what a simple blog post looks like:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id            </span><span class="token maybe-class-name">Int</span><span class="token plain">                 </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email         </span><span class="token known-class-name class-name">String</span><span class="token plain">              </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name          </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  password      </span><span class="token known-class-name class-name">String</span><span class="token plain">              </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">password</span><span class="token plain"> </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">omit</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts         </span><span class="token maybe-class-name">Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Post</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token maybe-class-name">Int</span><span class="token plain">      </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt </span><span class="token maybe-class-name">DateTime</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  updatedAt </span><span class="token maybe-class-name">DateTime</span><span class="token plain"> </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">updatedAt</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  content   </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  published </span><span class="token known-class-name class-name">Boolean</span><span class="token plain">  @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  viewCount </span><span class="token maybe-class-name">Int</span><span class="token plain">      @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author    </span><span class="token maybe-class-name">User</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token maybe-class-name">Int</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">     @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// logged-in users can view published posts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> published</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Based on the ZModel schema, ZenStack automatically creates well-structured, type-safe, and authorized CRUD APIs for your database. MCP's core value lies in its tools, which fundamentally consist of schema definitions and executable operations—specifically, the CRUD operations on your database for most SaaS applications. ZenStack can generate both directly from its schema, which can be safely called by LLM thanks to the access control policy. Thus, your primary task is to define the schema, and ZenStack manages everything else.</p>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="building-an-mcp-server-from-scratch">Building an MCP Server from Scratch<a class="hash-link" aria-label="Direct link to Building an MCP Server from Scratch" title="Direct link to Building an MCP Server from Scratch" href="https://zenstack.dev/blog/database-to-mcp#building-an-mcp-server-from-scratch">​</a></h2>
<p>So let me walk you through the process of turning your database into an MCP server with OAuth-based authentication and authorization from scratch. I will use the blog post mentioned above as a sample; you can adapt it for your app, and all you need to do is change the ZModel schema accordingly.</p>
<p>You can find the final running project below:</p>
<p><a href="https://github.com/jiashengguo/zenstack-mcp-auth" target="_blank" rel="noopener noreferrer">https://github.com/jiashengguo/zenstack-mcp-auth</a></p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="initialize-the-project">Initialize the Project<a class="hash-link" aria-label="Direct link to Initialize the Project" title="Direct link to Initialize the Project" href="https://zenstack.dev/blog/database-to-mcp#initialize-the-project">​</a></h3>
<p>Use the below script to initialize a project with Prisma and Express:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx try-prisma@latest -t orm/express -n blog-app-mcp</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Install the MCP SDK,  bcrypt(used to hash password):</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install @modelcontextprotocol/sdk, bcrypt, @types/bcrypt</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Initialize ZenStack:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack@latest init</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="auth">Auth<a class="hash-link" aria-label="Direct link to Auth" title="Direct link to Auth" href="https://zenstack.dev/blog/database-to-mcp#auth">​</a></h3>
<p>To support the OAuth, the server needs to store the following information:</p>
<ul>
<li>OAuth Client</li>
<li>Authorization Code</li>
<li>Access Token</li>
<li>Refresh Token</li>
</ul>
<p>So let’s add them in the ZModel schema file so that we can use the generated type-safe API to operate on them:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">OAuthClient</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id                       </span><span class="token maybe-class-name">Int</span><span class="token plain">      </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  client_id                </span><span class="token known-class-name class-name">String</span><span class="token plain">   </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  client_secret            </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">AuthorizationCode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id            </span><span class="token maybe-class-name">Int</span><span class="token plain">      </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  code          </span><span class="token known-class-name class-name">String</span><span class="token plain">   </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">AccessToken</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token maybe-class-name">Int</span><span class="token plain">      </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  token     </span><span class="token known-class-name class-name">String</span><span class="token plain">   </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">RefreshToken</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token maybe-class-name">Int</span><span class="token plain">      </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  token     </span><span class="token known-class-name class-name">String</span><span class="token plain">   </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>According to the MCP specification,  here are the three endpoints that the server must implement:</p>
<table><thead><tr><th>Endpoint</th><th>Url</th><th>Usage</th></tr></thead><tbody><tr><td>Authorization</td><td>/authorize</td><td>Used for authorization requests</td></tr><tr><td>Token</td><td>/token</td><td>Used for token exchange &amp; refresh</td></tr><tr><td>Registration</td><td>/register</td><td>Used for dynamic client registration</td></tr></tbody></table>
<p>MCP SDK provides a utility function, <code>mcpAuthRouter</code>, to install these standard authorization server endpoints.   What we need to do is implement an <code>OAuthServerProvider</code> to pass it to the <code>mcpAuthRouter</code>.</p>
<p>Let’s create an <code>AuthMiddleware</code> class to handle all the Auth logic, and a <code>PasswordAuthProvider</code> implementing the <code>OAuthServerProvider</code> to do the actual logic.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">AuthMiddleware</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> authProvider</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">PasswordAuthProvider</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> authRouter</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> express</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Router</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> express</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Router</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setupRouter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// Add OAuth router using the mcpAuthRouter function</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authRouter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">'/'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">mcpAuthRouter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                provider</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authProvider</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                issuerUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">baseUrl</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                baseUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">baseUrl</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                scopesSupported</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'write'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="passwordauthprovider">PasswordAuthProvider<a class="hash-link" aria-label="Direct link to PasswordAuthProvider" title="Direct link to PasswordAuthProvider" href="https://zenstack.dev/blog/database-to-mcp#passwordauthprovider">​</a></h4>
<p>Here are the three functions that will actually be called, corresponding to the three necessary endpoints  mentioned above:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">OAuthServerProvider</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token comment" style="color:#999988;font-style:italic">// /register</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">get</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">clientsStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">OAuthRegisteredClientsStore</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// /authorize</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">authorize</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">client</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">OAuthClientInformationFull</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> params</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">AuthorizationParams</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token keyword" style="color:#00009f">void</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// /token</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">exchangeAuthorizationCode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">client</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">OAuthClientInformationFull</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> authorizationCode</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> codeVerifier</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> redirectUri</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">OAuthTokens</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The <code>clientStore</code> deals with register and read through <code>registerClient</code> and <code>getClient</code> functions, it simply writes and reads the <code>OAuthClient</code> model from the database.</p>
<p>The actual authorization flow begins with <code>authorize</code>.  Its job is to redirect to the actual authorization server with all the necessary parameters.   Since in our case, the authorization server is itself,  let’s redirect to the<code>/auth/login</code> path:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> loginUrl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/auth/login'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">baseUrl</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">redirect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">loginUrl</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Therefore, we need to create a <code>login.html</code>  to serve it.     It’s a typical login form like below:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/ca2ffe72-82c2-4a3b-a65f-c8919c06e86f" alt="Login-Page" class="img_XvmG"></p>
<p>After clicking Login, it combines all the URL parameters passed from the authorization flow along with the email and passwords to post to the server endpoint <code>/auth/login</code>.</p>
<p>The server endpoint <code>/auth/login</code> would eventually call into the function <code>handlelogin</code> of  <code>PasswordAuthProvider</code>.   It verifies the user identity. If it is correct, then create an auth code, return to the client, and store it in an <code>AuthorizationCode</code> instance in the database</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Generate authorization code</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> authCode </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> crypto</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">randomBytes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">32</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'hex'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Store authorization code in database</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authorizationCode</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          code</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authCode</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          clientId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          codeChallenge</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          redirectUri</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          expiresAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">600000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 10 minutes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          scopes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>After getting the auth code returned from the server, <code>login.html</code> will redirect to the MCP client with the auth code.    Finally, the MCP client will use the auth code to call the <code>/token</code> endpoint to exchange for the access token, which will call into the <code>exchangeAuthorizationCode</code> function.</p>
<p>In <code>exchangeAuthorizationCode</code>, the server will first verify that the auth code it just granted is valid, and the client’s identification, such as PKCE.   If everything is correct, it will generate both access token and refresh token,  store them in the database, and return to the MCP client.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Store tokens in database</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">accessToken</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        token</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> accessToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        clientId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">client_id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        scopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">scopes</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        expiresAt</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">refreshToken</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        token</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> refreshToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        clientId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">client_id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        scopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">scopes</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        expiresAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">86400</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">30</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 30 days</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Clean up authorization code</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authorizationCode</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">delete</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> code</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authorizationCode </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    access_token</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> accessToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    token_type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Bearer'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    expires_in</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> expiresIn</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    refresh_token</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> refreshToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    scope</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">authData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">scopes</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">' '</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="stateful-multi-connections-streamable-http-server">Stateful Multi-connections Streamable HTTP Server<a class="hash-link" aria-label="Direct link to Stateful Multi-connections Streamable HTTP Server" title="Direct link to Stateful Multi-connections Streamable HTTP Server" href="https://zenstack.dev/blog/database-to-mcp#stateful-multi-connections-streamable-http-server">​</a></h3>
<p>After the MCP client gets the access token,  each time it communicates with the server through the main endpoint, it will put the access token in the Authorization header.  The server should use it to verify the client and get the client’s identity.    Let’s choose the conventional <code>/mcp</code> endpoint as the main endpoint and <code>getFlexibleAuthMiddleware</code> to do so.</p>
<p>The first request sent by the MCP client to the server is an <strong><code>initialize</code></strong> request. The server should respond with server information and a generated session ID, which the client would always include to maintain the session.  As a production-ready MCP server, it definitely should support multiple simultaneous connections.   Therefore, we use a map to store each client by its session ID.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> transports</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">StreamableHTTPServerTransport</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To manage the session for each user, we will create a dedicated <code>MCPServer</code> for it.  Since it’s per user per MCP server, we could store the <code>userId</code> in it, which will be used for Authorization when executing tools.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionId </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> transports</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    transport </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> transports</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">sessionId </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isInitializeRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Handle new MCP connection initialization</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    transport </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">StreamableHTTPServerTransport</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function-variable function" style="color:#d73a49">sessionIdGenerator</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> crypto</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">randomUUID</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function-variable function" style="color:#d73a49">onsessioninitialized</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">New MCP session initialized: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">sessionId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">, User ID: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">userId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            transports</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> transport</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> mcpServer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createMCPServer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> mcpServer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">connect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">transport</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">MCP server connected for User ID: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">userId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Handle connection close</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    transport</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access" style="color:#d73a49">onclose</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">transport</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">sessionId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">MCP session closed: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">transport</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">sessionId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">delete</span><span class="token plain"> transports</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">transport</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">sessionId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// invalid request</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Handle the request</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> transport</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">handleRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="tool">Tool<a class="hash-link" aria-label="Direct link to Tool" title="Direct link to Tool" href="https://zenstack.dev/blog/database-to-mcp#tool">​</a></h3>
<p>We will fully rely on the ZenStack to do the job.   Remember the two parts of the Tool: schema and execution.</p>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="schema">Schema<a class="hash-link" aria-label="Direct link to Schema" title="Direct link to Schema" href="https://zenstack.dev/blog/database-to-mcp#schema">​</a></h4>
<p>ZenStack has a native Zod plugin to generate the Zod schema for its CRUD API.  We can enable it by adding the one below to the <code>schema.zmodel</code> :</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">plugin zod </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@core/zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, after running <code>zenstack generate</code>,  it generates the Zod schema for all the operations it supports for every model in <code>@zenstackhq/runtime/zod/input</code>.  For example, this is the one for <code>Post</code> :</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> z </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'zod'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">Prisma</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'.zenstack/models'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">PostInputSchemaType</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findUnique</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostFindUniqueArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findFirst</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostFindFirstArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostFindManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    create</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostCreateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    createMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostCreateManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">delete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostDeleteArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    deleteMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostDeleteManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    update</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostUpdateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    updateMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostUpdateManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    upsert</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostUpsertArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    aggregate</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostAggregateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    groupBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostGroupByArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    count</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">PostCountArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>So each operation for each model will become a Tool of our MCP server.</p>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="execution">Execution<a class="hash-link" aria-label="Direct link to Execution" title="Direct link to Execution" href="https://zenstack.dev/blog/database-to-mcp#execution">​</a></h4>
<p>The execution of each function is very straightforward; just dynamically invoke the function with the argument generated by the LLM.   Therefore, the whole Tool creating logic is simply one lambda expression in less than 30 lines of code:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">McpServer</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@modelcontextprotocol/sdk/server/mcp.js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">crudInputSchema</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime/zod/input'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createMCPServer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> server </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">McpServer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">crudInputSchema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> modelNames</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">getModelName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> functions</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modelName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getModelName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">functions </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token maybe-class-name">Record</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> functionNames</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> toolName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">modelName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">_</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">functionName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    server</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">tool</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        toolName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Prisma client API '</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">functionName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">' function input argument for model '</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">modelName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">'. </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">currentUserPrompt</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> args </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Calling tool: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">toolName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> with args:</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">modelName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Tool </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">toolName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> returned:</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                                content</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'text'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> text</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> server</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>One thing that needs to be mentioned is that the Prisma client that is used to call that operation is the ZenStack-<code>enhanced</code> one that contains the current user identity, which is the <code>userId</code> stored in the <code>MCPServer</code> during initialization.  This will completely eliminate unauthorized data access, no matter what parameters LLM generates for the function, even if hallucination happens:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> enhance </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Gets a Prisma client bound to the current user identity</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> userId </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userId </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>There is another benefit that since these Tools are actually the standard Prisma Client API, it works well for the LLM:</p>
<ul>
<li>First, its declarative and well-structured nature makes it easy to understand and reason about, allowing LLMs to generate accurate and predictable queries with minimal ambiguity</li>
<li>Second, the API's flexibility allows one single call to access multiple models (database tables), enabling LLMs to efficiently navigate complex data relationships and perform sophisticated queries across different entities without requiring separate API calls or complex orchestration logic.</li>
<li>Third, Prisma has been widely adopted for years, providing a rich ecosystem of documentation, examples, and community usage that LLMs can learn from—greatly increasing the chance of generating correct and context-aware code.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="test">Test<a class="hash-link" aria-label="Direct link to Test" title="Direct link to Test" href="https://zenstack.dev/blog/database-to-mcp#test">​</a></h2>
<p>The sample project includes seed data for you to play around with. Simply run the below command to make it ready:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx prisma db push</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx prisma db seed</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>It creates three users with posts. The passwords for all users are&nbsp;<code>password123</code>.</p>
<ul>
<li><a href="mailto:alex@zenstack.dev" target="_blank" rel="noopener noreferrer">alex@zenstack.dev</a></li>
<li><a href="mailto:sarah@stripe.com" target="_blank" rel="noopener noreferrer">sarah@stripe.com</a></li>
<li><a href="mailto:jordan@vercel.com" target="_blank" rel="noopener noreferrer">jordan@vercel.com</a></li>
</ul>
<p>Enjoy playing it with whatever MCP client of your choice:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/3e59af8c-e7cf-4ed8-a953-7085e811faa2" alt="VSCode-Copilot" class="img_XvmG"></p>
<p>I’m really eager to hear about the results you achieved with your application! If you’re looking for any tips on writing the ZModel schema, please feel free to reach out to <a href="https://x.com/jiashenggo" target="_blank" rel="noopener noreferrer">me</a> or hop onto <a href="https://discord.gg/Ykhr738dUe" target="_blank" rel="noopener noreferrer">our Discord</a>. I’m always willing to help!</p>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>ai</category>
            <category>mcp</category>
            <category>database</category>
            <category>authorization</category>
        </item>
        <item>
            <title><![CDATA[How to Build AI Agents to Enhance  SaaS With Minimal Code and Effort]]></title>
            <link>https://zenstack.dev/blog/ai-agent</link>
            <guid>https://zenstack.dev/blog/ai-agent</guid>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A thought experiment on the roles AI can play in software development.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-14757b82ad8d431cc4870d73d9df2ee8.png" width="1536" height="900" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="is-saas-really-dead">Is SaaS Really Dead?<a class="hash-link" aria-label="Direct link to Is SaaS Really Dead?" title="Direct link to Is SaaS Really Dead?" href="https://zenstack.dev/blog/ai-agent#is-saas-really-dead">​</a></h2>
<p>Several months ago,  the internet was abuzz with the Microsoft CEO Satya Nadella saying, “SaaS is dead.”</p>
<p>The conversation started with a question from Bill Gurley about whether Satya was worried that newer startups are building applications with an AI-first approach, which could obfuscate traditional infrastructure like Excel or CRM.  Here is Satya’s response:</p>
<blockquote>
<p>I think, the notion that business applications exist, that's probably where they'll all collapse, right in the agent era because if you think about it, right, they are essentially <strong>CRUD databases with a bunch of business logic. The business logic is all going to these agents</strong>, and these agents are going to be multi repo CRUD</p>
</blockquote>
<p>Some people might assume he suggested that AI agents could or would replace SaaS. However, if you watch the entire podcast, you'll understand that he's actually discussing how AI agents will transform SaaS rather than replace it:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/9NtsnzRFJ_o?si=8jAuPYCPYzg1j7-4&amp;start=2770" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>
<p>How? Let me share an example from my experience. At my previous company, we used Trello for project management. I was impressed by its clean and efficient UX design.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/72af641a-25a5-4554-a07f-a6fe8bced243" alt="trello" class="img_XvmG"></p>
<p>However, one drawback that consistently bothered me was the lack of flexibility in performing queries.  For instance:</p>
<ul>
<li>How many cards were done last week?</li>
<li>Who has the most incomplete cards?</li>
<li>Which list has the most incomplete cards?</li>
</ul>
<p>Trello doesn't provide a direct way to show the answer; you'll need to do the math manually.  I understand that offering such flexibility from a UI design perspective is challenging,  yet each encounter often brings a sigh of frustration. 😮‍💨</p>
<p>Balancing flexibility and simplicity is a core challenge for SaaS. This is where an AI agent can play a role. The most straightforward solution is to introduce a chatbot that can easily provide answers to all the questions mentioned, without altering any existing features.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="challenge-of-building-an-ai-chatbot">Challenge of Building an AI Chatbot<a class="hash-link" aria-label="Direct link to Challenge of Building an AI Chatbot" title="Direct link to Challenge of Building an AI Chatbot" href="https://zenstack.dev/blog/ai-agent#challenge-of-building-an-ai-chatbot">​</a></h2>
<p>We are all aware of the hallucinations that AI causes.  But there is another level of hallucination:</p>
<p>AI makes many things seem easily accomplished on the surface, but you will see the challenge under the iceberg when it comes to production.</p>
<p>My previous company tried to integrate a chat agent into their SaaS product, and was struggling with those challenges:</p>
<ol>
<li>
<p><strong>LLM Failed to Generate the Correct API Call</strong></p>
<p>The initial plan was to have the LLM generate an API call directly based on user input. However, probably because the existing API is not well designed,  LLM often struggles to interpret and generate the correct parameters. Sometimes, it even calls the wrong API.</p>
</li>
<li>
<p><strong>The Complexity of Transforming the LLM Result</strong></p>
<p>Since the initial plan fails, they ask LLM to create the intermediate structured query object, then use code to convert this object into an actual database query.  The good thing is that it is the code that makes the final call; the bad thing is that the code can become highly complex as it needs to account for all possible cases the LLM might produce.</p>
<p>One critical issue that needs to be addressed is authorization. You have to scrutinize and ensure the generated query doesn’t break the authorization rule of the current system, which can sometimes be quite complex. Any oversight could lead to significant security breaches, potentially disastrous for a B2B SaaS.</p>
</li>
</ol>
<p>The fundamental issue seems to be that the current infrastructure is not friendly enough for AI agents, as Bill questioned Satya. So, what type of infrastructure would be suitable for AI?</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="schema-first-fit-ai-first">Schema-First Fit AI-First<a class="hash-link" aria-label="Direct link to Schema-First Fit AI-First" title="Direct link to Schema-First Fit AI-First" href="https://zenstack.dev/blog/ai-agent#schema-first-fit-ai-first">​</a></h2>
<p>Among all the discussions regarding Satya’s statement:</p>
<blockquote>
<p><strong>They are essentially CRUD databases with a bunch of business logic</strong>.The business logic is all going to these agents.</p>
</blockquote>
<p>Many people focus solely on the business logic that will be handled by the agent, often neglecting the essential prerequisite: <strong>the CRUD database</strong>. In other words, AI must accurately and precisely translate the business logic to the CRUD operation to ensure success. This is where the challenges arise, as illustrated in the previous example.  It seems there is a missing layer that focuses on 'what' rather than 'how' to make AI better work: <strong>a schema</strong>.  AI excels at declarative schemas over imperative code.   Therefore, if we could make the CRUD database a well-designed schema for AI to manipulate,  that could provide a robust foundation for the mission to be done.</p>
<p>The ZenStack schema-first toolkit is well-suited for the task. The core part is a DSL that unifies data modeling and access control, two essential parts of CRUD databases.  Here is an example of what a simple blog post looks like:</p>
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">enum</span><span class="token plain"> </span><span class="token maybe-class-name">Role</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">USER</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">ADMIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Post</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id        </span><span class="token known-class-name class-name">String</span><span class="token plain">  @id @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title     </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    published </span><span class="token known-class-name class-name">Boolean</span><span class="token plain"> @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    author    </span><span class="token maybe-class-name">User</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    authorId  </span><span class="token known-class-name class-name">String</span><span class="token plain">  @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> published </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">role</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ADMIN'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id       </span><span class="token known-class-name class-name">String</span><span class="token plain">  @id @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    email    </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> @unique</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    password </span><span class="token known-class-name class-name">String</span><span class="token plain">  @password @omit</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    role     </span><span class="token maybe-class-name">Role</span><span class="token plain">    @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">USER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    posts    </span><span class="token maybe-class-name">Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create,read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update,delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Based on the schema, ZenStack automatically generates well-structured, type-safe, and authorized CRUD APIs to the database.  Not only can AI understand and manipulate it better with less chance of hallucination, but also developers will be able to build the AI agent on top of it easily.</p>
<p>I know talk is cheap, so let me show you the code!</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="building-an-ai-chatbot-from-scratch">Building an AI Chatbot from Scratch<a class="hash-link" aria-label="Direct link to Building an AI Chatbot from Scratch" title="Direct link to Building an AI Chatbot from Scratch" href="https://zenstack.dev/blog/ai-agent#building-an-ai-chatbot-from-scratch">​</a></h2>
<p>Let’s build an AI chatbot for a Todo app to address the pain points of Trello earlier. To keep this concise, I'll focus on the key steps. You can find the link to the completed project on GitHub at the end of the post.  Here is the final outcome:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/abd5750c-ebc4-4fd3-85f1-286c4e2659e7" alt="zenstack-ai-chatbot" class="img_XvmG"></p>
<p>Here is the tech stack we are using:</p>
<ul>
<li><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a>&nbsp;- React framework</li>
<li><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a>&nbsp;- Full-stack toolkit with access control</li>
<li><a href="https://next-auth.js.org/" target="_blank" rel="noopener noreferrer">NextAuth</a> - Authentication for Next.js</li>
<li><a href="https://sdk.vercel.ai/" target="_blank" rel="noopener noreferrer">AI SDK</a>&nbsp;- AI integration for chat features</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-creating-the-project">1. Creating the project<a class="hash-link" aria-label="Direct link to 1. Creating the project" title="Direct link to 1. Creating the project" href="https://zenstack.dev/blog/ai-agent#1-creating-the-project">​</a></h3>
<p>Create a Next.js project with&nbsp;<code>create-t3-app</code>&nbsp;with Prisma, NextAuth, and TailwindCSS:</p>
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx create</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">t3</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">app@latest </span><span class="token operator" style="color:#393A34">--</span><span class="token plain">prisma </span><span class="token operator" style="color:#393A34">--</span><span class="token plain">nextAuth </span><span class="token operator" style="color:#393A34">--</span><span class="token plain">tailwind </span><span class="token operator" style="color:#393A34">--</span><span class="token plain">appRouter </span><span class="token operator" style="color:#393A34">--</span><span class="token constant" style="color:#36acaa">CI</span><span class="token plain"> todo</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">ai</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-initialize-the-project-for-zenstack">2. Initialize the project for ZenStack<a class="hash-link" aria-label="Direct link to 2. Initialize the project for ZenStack" title="Direct link to 2. Initialize the project for ZenStack" href="https://zenstack.dev/blog/ai-agent#2-initialize-the-project-for-zenstack">​</a></h3>
<p>Run the <code>zenstack</code> CLI to prepare your project for using ZenStack.</p>
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack@latest init</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Replace the <code>schema.zmodel</code> with the below content:</p>
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">generator client </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prisma-client-js"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">datasource db </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"sqlite"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    url      </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">env</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"DATABASE_URL"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">plugin zod </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@core/zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/*</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * Model for a Todo list</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">List</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id        </span><span class="token known-class-name class-name">String</span><span class="token plain">   @id @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">uuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    createdAt </span><span class="token maybe-class-name">DateTime</span><span class="token plain"> @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    updatedAt </span><span class="token maybe-class-name">DateTime</span><span class="token plain"> @updatedAt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    owner     </span><span class="token maybe-class-name">User</span><span class="token plain">     @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ownerId   </span><span class="token known-class-name class-name">String</span><span class="token plain">   @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title     </span><span class="token known-class-name class-name">String</span><span class="token plain">   @</span><span class="token function" style="color:#d73a49">length</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain">   </span><span class="token known-class-name class-name">Boolean</span><span class="token plain">  @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    todos     </span><span class="token maybe-class-name">Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// can be read by owner or space members (only if not private) </span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token keyword" style="color:#00009f">private</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// owner can do anything</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/*</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * Model for a single Todo</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Todo</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id          </span><span class="token known-class-name class-name">String</span><span class="token plain">    @id @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">uuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    createdAt   </span><span class="token maybe-class-name">DateTime</span><span class="token plain">  @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    updatedAt   </span><span class="token maybe-class-name">DateTime</span><span class="token plain">  @updatedAt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    owner       </span><span class="token maybe-class-name">User</span><span class="token plain">      @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ownerId     </span><span class="token known-class-name class-name">String</span><span class="token plain">    @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    list        </span><span class="token maybe-class-name">List</span><span class="token plain">      @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    listId      </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title       </span><span class="token known-class-name class-name">String</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">length</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    completedAt </span><span class="token maybe-class-name">DateTime</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// full access if the parent list is readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id       </span><span class="token known-class-name class-name">String</span><span class="token plain">  @id @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    name     </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    email    </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> @unique</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    password </span><span class="token known-class-name class-name">String</span><span class="token plain">  @password @omit</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    todo     </span><span class="token maybe-class-name">Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    list     </span><span class="token maybe-class-name">List</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// everyone can signup, and user profile is also publicly readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create,read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// only the user can update or delete their own profile</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update,delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This represents a multi-user Todo app.  Todo list can be private or public; list owners have full control over their list.  If a user can see the list, they can manipulate all the todos under that list.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3--implement-signupsignin">3.  Implement Signup/Signin<a class="hash-link" aria-label="Direct link to 3.  Implement Signup/Signin" title="Direct link to 3.  Implement Signup/Signin" href="https://zenstack.dev/blog/ai-agent#3--implement-signupsignin">​</a></h3>
<p>The tasks here are configuring <code>NextAuth</code> to use credential-based auth and creating the signup/signin form.  Check out the code for details.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="4-implement-the-chatbot-logic-with-vercel-ai-sdk-and-zenstack">4. Implement the chatbot logic with Vercel AI SDK and ZenStack<a class="hash-link" aria-label="Direct link to 4. Implement the chatbot logic with Vercel AI SDK and ZenStack" title="Direct link to 4. Implement the chatbot logic with Vercel AI SDK and ZenStack" href="https://zenstack.dev/blog/ai-agent#4-implement-the-chatbot-logic-with-vercel-ai-sdk-and-zenstack">​</a></h3>
<p>We will primarily utilize the AI SDK to develop the chatbot. It not only standardizes integration across various LLM providers but also offers a range of hooks for creating chat and generative user interfaces.   With that, building a chatbot with real-time message streaming requires just a few lines of code.  Here is the <a href="https://ai-sdk.dev/docs/ai-sdk-ui/chatbot" target="_blank" rel="noopener noreferrer">official doc</a>.</p>
<p>Simply put, it provides a <code>userChat</code> hook for the client and a corresponding server endpoint in <code>src/app/chat/route.ts</code>.  I will skip the UI part and focus on implementing the endpoint, which is essentially the complete functionality of this bot.</p>
<p>Every AI agent typically consists of two components: <strong>Tools and Prompts</strong>. As previously discussed, <strong>Tools</strong> in this context refer to the CRUD APIs of the database. So let’s see how it is getting implemented.</p>
<p>The AI SDK allows Zod Schema to be used to define the parameters of the Tool.  So, have you noticed a Zod plugin defined in the <code>schema.zmodel</code> ?</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">plugin zod </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@core/zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is a ZenStack native plugin that generates Zod schemas for models and input arguments of Prisma CRUD operations.   For instance, it will generate the CRUD schema for every model as below:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">TodoInputSchemaType</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findUnique</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoFindUniqueArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findFirst</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoFindFirstArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    findMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoFindManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    create</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoCreateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    createMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoCreateManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">delete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoDeleteArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    deleteMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoDeleteManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    update</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoUpdateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    updateMany</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoUpdateManyArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    upsert</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoUpsertArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    aggregate</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoAggregateArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    groupBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoGroupByArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    count</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">TodoCountArgs</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Therefore, all we need to do is convert each CRUD operation to an AI SDK <code>tool</code>.  Let’s create a <code>createToolsFromZodSchema</code> function iterates all the models:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">prismaInputSchema</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@zenstackhq/runtime/zod/input"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">Tool</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tool</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> zodSchema </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ai"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createToolsFromZodSchema</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">PrismaClient</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Record</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">Tool</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> functionNames </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"findMany"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"createMany"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"deleteMany"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"updateMany"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">inputTypeName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> functions</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">of</span><span class="token plain"> </span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prismaInputSchema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// remove the postfix InputSchema from the model name</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modelName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> inputTypeName</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"InputSchema"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> functionSchema</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">of</span><span class="token plain"> </span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      functions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> functionNames</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> functionParameterType </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">zodSchema</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        functionSchema </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodObject</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ZodRawShape</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          useReferences</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      tools</span><span class="token punctuation" style="color:#393A34">[</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">modelName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">_</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">functionName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">tool</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Prisma client API '</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">functionName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">' function input argument for model '</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">modelName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">'</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        parameters</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> functionParameterType</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function-variable function" style="color:#d73a49">execute</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Executing </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">modelName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">.</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">functionName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> with input:</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic">// eslint-disable-next-line</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">modelName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">functionName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-info admonition_al3P alert alert--info"><div class="admonitionHeading_Kjwq"><span class="admonitionIcon_gHdU"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_u1Ss"><p>To minimize the tool count and alleviate the AI's workload, we offer only four fundamental operations: "findMany," "createMany," "deleteMany," and "updateMany.”</p></div></div>
<p>The <code>execute</code> function inside is quite simple, just invoke the corresponding prisma client API function using the passed in <code>PrismaClient</code> object.  Here comes the most crucial part,  which is also the most exciting part of ZenStack:</p>
<p>The <code>PriamClient</code> object should be the <code>enhanced</code> Prisma client that contains the current user identity instead of the regular Prisma client:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">POST</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> messages </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> messages</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">CoreMessage</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> authObj </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> enhancedPrisma </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> authObj</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> tools </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createToolsFromZodSchema</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">enhancedPrisma</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><strong>The benefit is that no matter what parameters  AI generates for a function,  you never have to worry about unauthorized access.  This is because the access policy defined in the schema will always be injected under the hood before reaching the database.</strong></p>
<p>The system prompt is straightforward and general; you can review the code and tailor it to suit your specific requirements.</p>
<p>Thanks to the AI SDK and ZenStack, the entire server implementation of this chatbot was completed in less than 100 lines of code!   Even more impressive, this implementation is completely app-agnostic. In other words, regardless of your app (zmodel), it works seamlessly.</p>
<p>So if you're a ZenStack user, you now know the best practice for implementing an AI Chatbot. 😉</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="try-it-for-your-own-application">Try It for Your Own Application<a class="hash-link" aria-label="Direct link to Try It for Your Own Application" title="Direct link to Try It for Your Own Application" href="https://zenstack.dev/blog/ai-agent#try-it-for-your-own-application">​</a></h2>
<p>Here is the final project that you can run directly:</p>
<p><a href="https://github.com/jiashengguo/zenstack-ai-chatbot" target="_blank" rel="noopener noreferrer">https://github.com/jiashengguo/zenstack-ai-chatbot</a></p>
<p>The benefit is that you can easily test this AI chat agent for your own application — all you need to do is customize <code>schema.zmodel</code> to fit your application.</p>
<p>I would love to hear your feedback on it!</p>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>ai</category>
            <category>saas</category>
            <category>agent</category>
            <category>authorization</category>
        </item>
        <item>
            <title><![CDATA[ZenStack - The Next Chapter (Part III. New Plugin System)]]></title>
            <link>https://zenstack.dev/blog/next-chapter-3</link>
            <guid>https://zenstack.dev/blog/next-chapter-3</guid>
            <pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post explores the new plugin system of ZenStack v3.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-e8927896abe51ab9a3d5981c6bbfbc67.png" width="1536" height="1024" class="img_XvmG"></p>
<p>In the <a href="https://zenstack.dev/blog/next-chapter-2">previous post</a>, we discussed the new extensibility opportunities of the core ORM by adopting Kysely. In this post, we'll continue exploring the plan for v3's new plugin system that allows you to deeply customize ZenStack's behavior in a clean and maintainable way.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="plugin-composition">Plugin Composition<a class="hash-link" aria-label="Direct link to Plugin Composition" title="Direct link to Plugin Composition" href="https://zenstack.dev/blog/next-chapter-3#plugin-composition">​</a></h2>
<p>ZenStack v2 provided a rudimentary <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part2">plugin system</a> that allows you to participate in the process of <code>zenstack generate</code>. It's sufficient for use cases like generating OpenAPI specs or TanStack Query hooks. However, there's no well-defined way to extend the ORM's runtime behavior in a pluggable way.</p>
<p>V3 aims to provide a more complete plugin system that allows you to contribute at the schema, generation, and runtime levels. A plugin can include the following parts:</p>
<ol>
<li>
<p>A <code>plugin.zmodel</code> file that can define attributes, functions, procedures, etc.
V2 already offers this. When running <code>zenstack generate</code>, all ZModel contributions from plugins will be merged with the user ZModel files.</p>
</li>
<li>
<p>A generator function that's called during generation
V2 already offers this. The function will be called at <code>zenstack generate</code> time and given the ZModel AST (and potentially the Prisma DMMF too, TBD) as input. The generator can interpret attributes, functions, etc. defined in <code>plugin.zmodel</code>.</p>
</li>
<li>
<p>A runtime plugin class that implements various callbacks
This provides great flexibility for a plugin to hook into the ORM's lifecycle at various levels - more about this in the next section.</p>
</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="runtime-plugin">Runtime Plugin<a class="hash-link" aria-label="Direct link to Runtime Plugin" title="Direct link to Runtime Plugin" href="https://zenstack.dev/blog/next-chapter-3#runtime-plugin">​</a></h2>
<p>A runtime plugin is an object satisfying the following interface. It may look a bit complex because it contains callbacks for different purposes. Don't worry, we'll dissect them shortly.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">RuntimePlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Plugin ID.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Intercepts an ORM query.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  onQuery</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> PluginContext</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    proceed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> ProceedQueryFunction</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">unknown</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Kysely query transformation.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  transformKyselyQuery</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> PluginTransformKyselyQueryArgs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> RootOperationNode</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Kysely query result transformation.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  transformKyselyResult</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> PluginTransformKyselyResultArgs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">QueryResult</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">UnknownRow</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * This callback determines whether a mutation should be intercepted, and if so,</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * what data should be loaded before and after the mutation.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  mutationInterceptionFilter</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> MutationHooksArgs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> MaybePromise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">MutationInterceptionFilterResult</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Called before an entity is mutated.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  beforeEntityMutation</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> PluginBeforeEntityMutationArgs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> MaybePromise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token keyword" style="color:#00009f">void</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * Called after an entity is mutated.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  afterEntityMutation</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    args</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> PluginAfterEntityMutationArgs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> MaybePromise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token keyword" style="color:#00009f">void</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To install a plugin, simply call the client's <code>$use</code> method to pass in its definition.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZenStackClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-plugin'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">onQuery</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> proceed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="orm-query-interception">ORM Query Interception<a class="hash-link" aria-label="Direct link to ORM Query Interception" title="Direct link to ORM Query Interception" href="https://zenstack.dev/blog/next-chapter-3#orm-query-interception">​</a></h3>
<p>A high-level way of hooking into the ORM's lifecycle is to intercept the CRUD calls: <code>create</code>, <code>update</code>, <code>findMany</code>, etc. The <code>args</code> parameter contains the model (e.g., "User"), the operation (e.g., "findMany"), and the arguments (e.g.: <code>{ where: { id: 1 } }</code>). The <code>proceed</code> parameter is an async function that triggers the CRUD's execution. Things you can do include:</p>
<ol>
<li>Executing arbitrary code before and after calling <code>proceed</code>.</li>
<li>Altering the query arguments.</li>
<li>Transforming the query results.</li>
<li>Overriding the call completely without calling <code>proceed</code>.</li>
</ol>
<p>Here's an example of logging slow queries:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZenStackClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'slow-query-logger'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">onQuery</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> proceed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> start </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">proceed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">queryArgs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> duration </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> start</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">duration </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        logger</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Slow query: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">args</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">model</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">args</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">operation</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation constant" style="color:#36acaa">JSON</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation function" style="color:#d73a49">stringify</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation">args</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">queryArgs</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The plugin can also have side effects by making extra ORM calls (the <code>args</code> parameter includes the current <code>ZenStackClient</code> instance), or even start a transaction to group multiple operations.</p>
<p>ORM query interception is useful for many scenarios, but it doesn't intercept CRUD made with the query builder API:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// the following call will not trigger the plugin's `onQuery` callback</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$qb</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">selectFrom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'User'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">leftJoin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.email'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.title'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">execute</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To ubiquitously handle all database operations, whether originating from ORM call or query builder, use the Kysely transformers explained in the next section.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="kysely-transformation">Kysely Transformation<a class="hash-link" aria-label="Direct link to Kysely Transformation" title="Direct link to Kysely Transformation" href="https://zenstack.dev/blog/next-chapter-3#kysely-transformation">​</a></h3>
<p>Kysely has a built-in <a href="https://kysely.dev/docs/plugins" target="_blank" rel="noopener noreferrer">plugin mechanism</a> that allows you to transform the SQL-like query tree before execution and transform the query result before returning to the caller. ZenStack v3 will leverage it directly in its plugin system. Here's an example for automatically attaching prefixes to id fields during insert:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> OperationNodeTransformer </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'kysely'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZenStackClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">$use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'id-prefixer'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">transformKyselyQuery</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">node</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">InsertQueryNode</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">is</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">node</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> node</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">IdPrefixTransformer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">transform</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">node</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// a transformer that recursively visit the node and prefix primary key</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// assignment values</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">IdPrefixTransformer</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">OperationNodeTransformer</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>No matter how you access the database with ZenStack, using the ORM API or query builder, eventually, an operation is transformed into a Kysely query tree and then executed. Intercepting at the Kysely level allows you to preprocess and post-process all database queries uniformly.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="entity-mutation-hooks">Entity Mutation Hooks<a class="hash-link" aria-label="Direct link to Entity Mutation Hooks" title="Direct link to Entity Mutation Hooks" href="https://zenstack.dev/blog/next-chapter-3#entity-mutation-hooks">​</a></h3>
<p>Sometimes, you don't care about what SQL is executed, but instead, you want to tap into entity mutation events: entities created, updated, or deleted. You can intercept entity mutations via <a href="https://zenstack.dev/blog/next-chapter-3#orm-query-interception">ORM Query Interception</a> or <a href="https://zenstack.dev/blog/next-chapter-3#kysely-transformation">Kysely Transformation</a>, but it can be rather complex to implement.</p>
<p>The entity mutation hooks are designed to make this scenario easy. It allows you to directly provide callbacks invoked before and after a mutation happens. There are a few complexities to consider, though:</p>
<ol>
<li>Accessing the entities pre/post mutation</li>
</ol>
<p>It's often desirable to be able to inspect entities impacted by the mutation (before and after). However, loading the entities can be expensive, especially for mutations affecting many rows.</p>
<ol start="2">
<li>Side effects and transaction</li>
</ol>
<p>There can be cases where you want to have database side effects in your callbacks, and you may wish your side effects and the original mutation to happen atomically. However, unconditionally employing a transaction for every mutation can bring unnecessary overhead.</p>
<p>To mitigate these problems, a runtime plugin allows you to provide an additional <code>mutationInterceptionFilter</code> callback to "preflight" an interception. The callback gives you access to the mutation that's about to happen and lets you return several pieces of information that control the interception:</p>
<ul>
<li>If the mutation should be intercepted at all</li>
<li>If the pre-mutation entities should be loaded and passed to the <code>beforeEntityMutation</code> call</li>
<li>If the post-mutation entities should be loaded and passed to the <code>afterEntityMutation</code> call</li>
<li>If a transaction should be used to wrap around the before/after hooks call and the mutation itself</li>
</ul>
<p>By carefully implementing the "preflight" callback, you can minimize the performance impact caused by the mutation hooks.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/next-chapter-3#conclusion">​</a></h2>
<p>ZenStack v3's core part will focus on providing the database access primitives, and most of the upper-level features will be implemented as plugins. This can include access control, soft delete, encryption, etc. An emphasis on extensibility will allow developers to adapt the ORM to the exact needs of their applications.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>zenstack</category>
            <category>v3</category>
        </item>
        <item>
            <title><![CDATA[ZenStack - The Next Chapter (Part II. An Extensible ORM)]]></title>
            <link>https://zenstack.dev/blog/next-chapter-2</link>
            <guid>https://zenstack.dev/blog/next-chapter-2</guid>
            <pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post explores how ZenStack V3 will become a more extensible ORM.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-e8927896abe51ab9a3d5981c6bbfbc67.png" width="1536" height="1024" class="img_XvmG"></p>
<p>In the <a href="https://zenstack.dev/blog/next-chapter-1">previous post</a>, we discussed the general plan for ZenStack v3 and the big refactor. This post will explore the extensibility opportunities the new architecture brings to the core ORM.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="query-builder">Query Builder<a class="hash-link" aria-label="Direct link to Query Builder" title="Direct link to Query Builder" href="https://zenstack.dev/blog/next-chapter-2#query-builder">​</a></h2>
<p>While continuing to provide the fully typed CRUD API like <code>PrismaClient</code> (<code>findMany</code>, <code>create</code>, etc.), using <a href="https://kysely.dev/" target="_blank" rel="noopener noreferrer">Kysely</a> as the underlying data access layer allows us to easily offer a low-level, query-builder style API too.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// CRUD API (the same as PrismaClient)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> posts</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Query builder API (backed by Kysely)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$qb</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">selectFrom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'User'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">leftJoin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.email'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post.title'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">execute</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Both the CRUD API and the query builder API are automatically inferred from the ZModel schema, so you don't need any extra configuration to use them, and they're always consistent. Given Kysely query builder's awesome flexibility, we believe you'll rarely need to resort to raw SQL anymore.</p>
<p>What's even more powerful is that you can blend query builder into CRUD calls. For complex queries, you can still enjoy the terse syntax of the CRUD API, and mix in the query builder for extra expressiveness. Here's an example:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    age</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> gt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">18</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// "eb" is a Kysely expression builder</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">$expr</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">eb</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">eb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'email'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'like'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'%@zenstack.dev'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>It's similar to the long-awaited <a href="https://github.com/prisma/prisma/issues/5560" target="_blank" rel="noopener noreferrer">whereRaw</a> feature request in Prisma, but better thanks to Kysely's type-safe query builder. You can implement arbitrarily complex filters involving joins or subqueries with the <code>$expr</code> clause.</p>
<p>Kysely's query builder syntax may take some time to get used to, but once you get the hang of it, it's pleasant to write and incredibly powerful.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="computed-fields">Computed Fields<a class="hash-link" aria-label="Direct link to Computed Fields" title="Direct link to Computed Fields" href="https://zenstack.dev/blog/next-chapter-2#computed-fields">​</a></h2>
<p>One major limitation of Prisma and ZenStack v2 is the lack of real "computed fields". Prisma's client extension allows you to <a href="https://www.prisma.io/docs/orm/prisma-client/queries/computed-fields" target="_blank" rel="noopener noreferrer">add custom fields to models</a>, but it's purely client-side. It's good for simple computations like combining <code>firstName</code> and <code>lastName</code> into full name, but you can't do anything that needs database-side computation, like adding a <code>postCount</code> field to return the number of posts a user has.</p>
<p>ZenStack v3 is determined to solve this problem. It'll introduce a new <code>@computed</code> attribute that allows you to define computed fields in ZModel.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> Post</span><span class="token type-class-name"></span><br></span><span class="token-line" style="color:#393A34"><span class="token type-class-name">  postCount</span><span class="token plain"> </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@computed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Of course, you'll need to provide an "implementation" for computing the field, on the database side. Again, Kysely's query builder is perfect for this. When creating a ZenStack client instance, you'll need to provide a callback that returns a Kysely expression builder for each computed field.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> ZenStackClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZenStackClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  computed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    user</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// select count(*) as postCount from Post where Post.authorId = User.id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">postCount</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">eb</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        eb</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">selectFrom</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">whereRef</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Post.authorId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'='</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'User.id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> fn </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> fn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token generic-function function" style="color:#d73a49">countAll</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">as</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'postCount'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, when you query the <code>User</code> model, the <code>postCount</code> field will be returned in the result. You can also use it to filter, sort, etc., just like any other field.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// find users with more than 10 posts and sort by post count</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> users </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> postCount</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> gt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  orderBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> postCount</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'desc'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Since the fields are declared in ZModel, you can use it in access policies as well:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> postCount </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Another benefit of having the computed fields declared in ZModel is that it'll be visible to all tools that consume the schema. For example, The OpenAPI spec generator can include them as fields in the generated APIs.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="custom-procedures">Custom Procedures<a class="hash-link" aria-label="Direct link to Custom Procedures" title="Direct link to Custom Procedures" href="https://zenstack.dev/blog/next-chapter-2#custom-procedures">​</a></h2>
<p>An ORM provides a set of data access primitives that allow applications to compose them into higher-level operations with business meaning. Such composition can be encapsulated in many ways: utility functions, application services, database stored procedures, etc. ZenStack v3 will introduce a new <code>proc</code> construct to allow defining such encapsulation in ZModel.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> SignUpInput </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">proc</span><span class="token type-class-name"> signUp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> SignUpInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> User</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Again, when creating a ZenStack client instance, you must provide implementations for the procedures.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> ZenStackClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZenStackClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  procs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">signUp</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">client</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// create user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// send a welcome email</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sendWelcomeEmail</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, you can call the type-safe procedures just like any other CRUD operation:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$procs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">signUp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> name </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You may wonder why we bother to declare the procedure in ZModel. Again, the purpose is to make them visible to upstream tools. For example, ZenStack's auto CRUD Http API can expose them as endpoints:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">POST /api/$procs/signUp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "email": ...,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "name": ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>And, for the frontend, TanStack query hooks can be used to call the procedures:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useHooks </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/tanstack-query/react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> schema </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'~/schema'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">SignUp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> crud </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useHooks</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> mutateAsync</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> signUp </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> crud</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">$procs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">signUp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleSubmit</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">signUp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">form</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onSubmit</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleSubmit</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* form fields */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">form</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Combined with access policies, custom procedures provide a powerful way to encapsulate complex business logic and expose them as APIs with minimum effort.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/next-chapter-2#conclusion">​</a></h2>
<p>ZenStack v3 will be a big step forward in terms of extensibility and flexibility. The new architecture allows us to provide a more powerful and expressive ORM experience while still maintaining simplicity and ease of use.</p>
<p>In the <a href="https://zenstack.dev/blog/next-chapter-3">next post</a>, we'll continue to explore how the new plugin system will allow you to make deep customizations to ZenStack in a clean and maintainable way.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>zenstack</category>
            <category>v3</category>
        </item>
        <item>
            <title><![CDATA[ZenStack - The Next Chapter (Part I. Overview)]]></title>
            <link>https://zenstack.dev/blog/next-chapter-1</link>
            <guid>https://zenstack.dev/blog/next-chapter-1</guid>
            <pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Looking into the future of ZenStack.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-e8927896abe51ab9a3d5981c6bbfbc67.png" width="1536" height="1024" class="img_XvmG"></p>
<p>Back in late 2022, when Jiasheng and I discussed how to make building web apps less painful, we initially thought of building something for people without a programming background. We eventually couldn't convince ourselves it could work, so we decided to take a step back and try to make developers' lives easier. That "step back" became the start of <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a>.</p>
<p>The past two and half years have been full of joy and fulfillment. By building the tool, seeing how people use it, and learning what works and what doesn't, we've got a better understanding of the intricacy of "easy". What people need is not just writing less code or shipping faster, but rather an inexplicable balance between low cognition burden and high flexibility. We are probably on the right track to solving the problem, but there's still a long way ahead. While considering what to do in V3, we think it's a good time to better align ZenStack's architecture with the ultimate goal.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-retrospect">A retrospect<a class="hash-link" aria-label="Direct link to A retrospect" title="Direct link to A retrospect" href="https://zenstack.dev/blog/next-chapter-1#a-retrospect">​</a></h2>
<p>ZenStack started its journey as a powerful extension package to Prisma ORM. It's been advocating a model-first approach, which is to use a rich and coherent schema as the core of an application and, from it, automatically derive as many workpieces as possible - access control, RESTful APIs, frontend hooks, Zod schemas, etc. As we went deeper into this path, we started to feel more and more constrained by our foundation - Prisma. Here are some of the most important limitations.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-you-cant-control-the-sql-to-be-executed">1. You can't control the SQL to be executed<a class="hash-link" aria-label="Direct link to 1. You can't control the SQL to be executed" title="Direct link to 1. You can't control the SQL to be executed" href="https://zenstack.dev/blog/next-chapter-1#1-you-cant-control-the-sql-to-be-executed">​</a></h3>
<p>One of the most significant extensions ZenStack made to Prisma is access control. It works by injecting into PrismaClient's queries. For example, with the following schema</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token maybe-class-name">Int</span><span class="token plain"> </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token maybe-class-name">Post</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// user can be read if he has at least one published post</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> posts</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">published </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>, a query like the following</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">findFirst</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>becomes this after ZenStack’s injection:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">findFirst</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> posts</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> some</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> published</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>It all works great until you run into a requirement that <a href="https://github.com/prisma/prisma/issues/8935" target="_blank" rel="noopener noreferrer">can't be expressed with a Prisma query</a>, like:</p>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token keyword" style="color:#00009f">User</span><span class="token plain"> {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// A user is readable if he has more than one post</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// The policy rule is pseudo and currently unsupported in ZenStack</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @</span><span class="token variable" style="color:#36acaa">@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">count</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">posts</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>But it can be easily achieved with SQL:</p>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"User"</span><span class="token plain"> u</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">JOIN</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Post"</span><span class="token plain"> p </span><span class="token keyword" style="color:#00009f">ON</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"id"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"authorId"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">BY</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"id"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">HAVING</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">COUNT</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Since Prisma doesn't provide a way to intercept and alter the SQL (or its equivalence) before execution, we don't have the option to inject at a lower level.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-lacking-a-good-enough-escape-hatch">2. Lacking a (good enough) escape hatch<a class="hash-link" aria-label="Direct link to 2. Lacking a (good enough) escape hatch" title="Direct link to 2. Lacking a (good enough) escape hatch" href="https://zenstack.dev/blog/next-chapter-1#2-lacking-a-good-enough-escape-hatch">​</a></h3>
<p>Prisma's query API is great, but when your requirements outgrow its capability, you'll need to resort to SQL. This is not ideal for several reasons:</p>
<ul>
<li>Writing SQL can be challenging for developers and feels like a considerable DX degradation compared to the awesome fully-typed query API</li>
<li>SQL codes are quite often not portable between different database types</li>
<li>People have repeatedly asked if "ZenStack supports access control for raw SQL or typed-SQL". We don't want to get into the business of parsing and injecting multiple dialects of SQL.</li>
</ul>
<p>Instead, a query-builder style API can probably cover most use cases where raw SQL is needed and provides a much better DX experience while making injection a lot easier.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3-excessive-stack-complexity">3. Excessive stack complexity<a class="hash-link" aria-label="Direct link to 3. Excessive stack complexity" title="Direct link to 3. Excessive stack complexity" href="https://zenstack.dev/blog/next-chapter-1#3-excessive-stack-complexity">​</a></h3>
<p>Sitting above Prisma, ZenStack needs to interface with Prisma at multiple levels:</p>
<ul>
<li>Transpiling ZModel to Prisma Schema</li>
<li>Manipulating Prisma-generated TypeScript types</li>
<li>Generating more code (Zod, frontend hooks, etc., based on <a href="https://github.com/prisma/prisma/blob/main/ARCHITECTURE.md#the-dmmf-or-data-model-meta-format" target="_blank" rel="noopener noreferrer">Prisma’s DMMF</a>)</li>
<li>Intercepting PrismaClient APIs at runtime</li>
</ul>
<p>Besides implementation complexity, a significant price that this design pays is the slow code generation process for large schemas.</p>
<p>Despite these claims, we don't intend to say Prisma is incompetent in any way. Quite on the contrary, we love it, learned a lot from it, and think it's the best in class for many typical applications. We're simply reaching the conclusion that Prisma is not the best abstraction level that ZenStack builds above to achieve our vision of the project.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="whats-next">What's next?<a class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" href="https://zenstack.dev/blog/next-chapter-1#whats-next">​</a></h2>
<p>The two primary goals we want to achieve with V3 are:</p>
<ul>
<li>Better flexibility</li>
<li>Snappier DX</li>
</ul>
<p>The plan is to migrate away from Prisma and reimplement the core ORM part with <a href="https://kysely.dev/" target="_blank" rel="noopener noreferrer">Kysely</a>. We know it sounds very bold and even scary for current ZenStack users. What about backward compatibility? Why Kysely?</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="backward-compatibility">Backward compatibility<a class="hash-link" aria-label="Direct link to Backward compatibility" title="Direct link to Backward compatibility" href="https://zenstack.dev/blog/next-chapter-1#backward-compatibility">​</a></h3>
<p>Despite the big refactor, the following compatibility guarantees will be kept:</p>
<ol>
<li>ZModel will be fully backward compatible.</li>
<li>The new ORM CRUD API will be fully compatible with <code>PrismaClient</code>.</li>
<li>Migrations previously generated with Prisma will continue to work (we'll likely continue using Prisma for database migration for a while).</li>
</ol>
<p>These should minimize the need for code changes during the upgrade. However, it doesn't mean no changes at all. For example, if you reference Prisma-generated TypeScript types explicitly, such references may need to be updated.</p>
<p>One fuzzy area is Prisma's client extensions. Given the many limitations of its design, we're not sure if making a compatible implementation is a good idea. We'll share more thoughts about extensibility in the follow-up posts.</p>
<p>We also understand that not everyone can jump onto the new major version soon after it's out. The support of V2 will continue for an extended period of time, until V3 is ready for adoption by the majority.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="why-kysely">Why Kysely?<a class="hash-link" aria-label="Direct link to Why Kysely?" title="Direct link to Why Kysely?" href="https://zenstack.dev/blog/next-chapter-1#why-kysely">​</a></h3>
<p>Kysely is a very popular, strongly typed SQL query builder. Using it as the database access layer can address several limitations mentioned previously.</p>
<ol>
<li>
<p>ZenStack can provide strongly typed ORM API (as <code>PrismaClient</code>) as well as strongly typed low-level query builder API. A query builder can almost express everything SQL can, with a much better DX. This is similar to what <a href="https://orm.drizzle.team/" target="_blank" rel="noopener noreferrer">Drizzle</a> offers.</p>
</li>
<li>
<p>Access control injection (and other query transformations) can be done at Kysely's query tree level, which offers much more flexibility than injecting <code>PrismaClient</code>.</p>
</li>
<li>
<p>Kysely's expression builder can be used as a generic extensibility mechanism (more about this in the <a href="https://zenstack.dev/blog/next-chapter-2">next post</a>).</p>
</li>
</ol>
<p>One question we repeatedly got was, "Why not build above Drizzle, given its rising popularity?". Drizzle is an excellent ORM that addresses some of Prisma's issues. However, the primary decision factor is the abstraction level. ZenStack needs a simple yet flexible database access layer as a foundation. Kysely satisfies this criterion perfectly, while Prisma, Drizzle, and other ORMs are too high-level and comprehensive.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="tell-us-what-you-think">Tell us what you think<a class="hash-link" aria-label="Direct link to Tell us what you think" title="Direct link to Tell us what you think" href="https://zenstack.dev/blog/next-chapter-1#tell-us-what-you-think">​</a></h2>
<p>Such a big refactor is always challenging and exciting at the same time. We believe it's necessary and will pave the road for ZenStack's future - a versatile ORM, a secured data layer, an API generator, and an indispensable tool for full-stack development. <a href="https://discord.com/channels/1035538056146595961/1352359627525718056" target="_blank" rel="noopener noreferrer">Let us know your thoughts!</a></p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>zenstack</category>
            <category>v3</category>
        </item>
        <item>
            <title><![CDATA[Code as Doc: Automate by Vercel AI SDK and ZenStack for Free]]></title>
            <link>https://zenstack.dev/blog/code-as-doc</link>
            <guid>https://zenstack.dev/blog/code-as-doc</guid>
            <pubDate>Mon, 23 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduce a ZenStack markdown plugin that generates documentation from the ZModel schema using Vercel AI SDK.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-df7d23ec126c88a9832aecbef97d9840.png" width="1792" height="1024" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="few-developers-like-writing-document">Few developers like writing document<a class="hash-link" aria-label="Direct link to Few developers like writing document" title="Direct link to Few developers like writing document" href="https://zenstack.dev/blog/code-as-doc#few-developers-like-writing-document">​</a></h2>
<p>If you have ever worked as a developer in a large company, you know that coding is just one small part of the daily responsibilities. One of Google's full-stack software engineers, Ray Farias, once estimated that developers write about <a href="https://www.quora.com/How-many-lines-of-code-get-written-at-Google-each-day" target="_blank" rel="noopener noreferrer">100-150 lines of code per day at Google</a>. While this estimate may vary across different teams, the order of magnitude matches my observations as a developer at Microsoft.</p>
<p>So where does the time go? A significant portion goes to activities like meetings, code reviews, planning sessions, and documentation tasks. Among all these tasks, documentation is my least favorite—and I suspect many other teammates feel the same way.</p>
<p>The main reason is that we didn't see much value in it. We were required to write design documents at the start of each sprint before coding, and after reviewing them with each other, most would remain unchanged forever. I can't count how many times I found something strange in a document only to have its author tell me it was outdated. 😂  Why don't we update the docs? Because our boss considers it less important than fixing bugs or adding new features.</p>
<p>Documentation should serve as a high-level abstraction of code to aid understanding. When documentation falls out of sync with the code, it loses its purpose. However, keeping doc synchronized with code requires effort—something few people actually enjoy.</p>
<p>Uncle Bob(Robert C. Martin)  has a famous quote about clean code:</p>
<blockquote>
<p>Good code serves as its own comments</p>
</blockquote>
<p>I think it would be great if this principle could be extended to documentation as well:</p>
<p><strong>Good code is its own best documentation</strong></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="generate-document-using-ai">Generate document using AI<a class="hash-link" aria-label="Direct link to Generate document using AI" title="Direct link to Generate document using AI" href="https://zenstack.dev/blog/code-as-doc#generate-document-using-ai">​</a></h2>
<p>There is a simple rule for the current trend in AI adoption: if humans don't enjoy doing something, let AI handle it. Documentation seems to fit perfectly into this category, especially as more and more code has already been generated by AI nowadays.</p>
<p>The timing couldn't be better, as GitHub has just announced that <a href="https://code.visualstudio.com/blogs/2024/12/18/free-github-copilot" target="_blank" rel="noopener noreferrer">Copilot features are free</a>. You could simply try to let it generate the documentation for your project for free.  However, the result might not be as good as you expected. Is it because your prompt isn't good enough? Maybe, but there's a more essential reason behind this:</p>
<p>LLMs don't handle imperative code as well as they handle declarative text.</p>
<p>Imperative code often involves complex control flow, state management, and intricate dependencies. This procedural nature requires a deeper understanding of the intent behind the code, which can be difficult for LLMs to infer accurately. Moreover, the larger the code volume, the more likely the result will be inaccurate and less informative.</p>
<p>What's the first thing you want to see in a web application's documentation? Most likely, it's the data models that serve as the foundation of the entire application. Can data models be defined declaratively? Absolutely! <a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma ORM</a> has already done a great job by allowing developers to define their application models **in an intuitive data modeling language.</p>
<p>The ZenStack toolkit, built on top of Prisma, enhances the schema with additional capabilities. By defining access policies and validation rules directly within the data model, it becomes the single source of truth for the backend of your application.</p>
<p>When I say "single source of truth," it contains not only all the information for the backend—it actually is your entire backend.  ZenStack automatically generates APIs and corresponding frontend hooks for you. With access policies defined, these can be safely called directly from the frontend without needing to enable row-level security (RLS) in the database layer.  Or, in another way,  you’ll hardly need to write any code for your backend.</p>
<p>Here is an extremely simplified example of a blog post app:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">datasource db </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'postgresql'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">env</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'DATABASE_URL'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">generator js </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'prisma-client-js'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">plugin hooks </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/tanstack-query'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    output </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'lib/hooks'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">enum</span><span class="token plain"> </span><span class="token maybe-class-name">Role</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">USER</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">ADMIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Post</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id        </span><span class="token known-class-name class-name">String</span><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title     </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    published </span><span class="token known-class-name class-name">Boolean</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    author    </span><span class="token maybe-class-name">User</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    authorId  </span><span class="token known-class-name class-name">String</span><span class="token plain">  @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> published </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">role</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ADMIN'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id       </span><span class="token known-class-name class-name">String</span><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    name     </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    email    </span><span class="token known-class-name class-name">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">unique</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    password </span><span class="token known-class-name class-name">String</span><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">password</span><span class="token plain"> </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">omit</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    role     </span><span class="token maybe-class-name">Role</span><span class="token plain">    @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">USER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    posts    </span><span class="token maybe-class-name">Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create,read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update,delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>We can easily create a tool that generates documentation from this schema using AI. You don't have to manually write and maintain docs anymore—simply integrate the generation process into the CI/CD pipeline, and there's no out-of-sync problem anymore. Here's an example of documentation generated from the schema:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/85218aa2-f7ff-4a43-90a3-afc35ee5ab4a" alt="zenstack-doc" class="img_XvmG"></p>
<p>I will walk you through the steps of how to create this tool.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="zenstack-plugin-system">ZenStack plugin system<a class="hash-link" aria-label="Direct link to ZenStack plugin system" title="Direct link to ZenStack plugin system" href="https://zenstack.dev/blog/code-as-doc#zenstack-plugin-system">​</a></h2>
<p>Like many wonderful tools in the web development world, ZenStack adopts a plugin-based architecture. At the core of the system is the ZModel schema, around which features are implemented as plugins.   Let's create a plugin to generate a markdown for a ZModel so it can be easily adopted by others.</p>
<blockquote>
<p>For brevity, we'll focus on core parts. See the <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part2/writing-plugins" target="_blank" rel="noopener noreferrer">ZenStack documentation</a> for complete plugin development details.</p>
</blockquote>
<p>A plugin is simply a Node.js module that has the two parts:</p>
<ol>
<li>A named export&nbsp;<code>name</code>&nbsp;that specifies the name of the plugin used for logging and error reporting.</li>
<li>A default function export containing the plugin logic.</li>
</ol>
<p>Here's what it looks like:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">PluginOptions</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/sdk'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DMMF</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/sdk/prisma'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">Model</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/sdk/ast'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ZenStack MarkDown'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Model</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">PluginOptions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dmmf</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DMMF</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Document</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><code>model</code> is the ZModel AST. It is the result object model of parsing and linking the ZModel schema, which is a tree structure containing all the information in the schema.</p>
<p>We can use <code>ZModelCodeGenerator</code> provided by ZenStack sdk to get the ZModel content from the AST.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">ZModelCodeGenerator</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/sdk'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> zModelGenerator </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ZModelCodeGenerator</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> zmodel </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> zModelGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">generate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now that we have the ingredients let's have AI do the cooking.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="use-vercel-ai-sdk-to-generate-document">Use Vercel AI SDK to generate document<a class="hash-link" aria-label="Direct link to Use Vercel AI SDK to generate document" title="Direct link to Use Vercel AI SDK to generate document" href="https://zenstack.dev/blog/code-as-doc#use-vercel-ai-sdk-to-generate-document">​</a></h2>
<p>Initially, I planned to use OpenAI to do the job. But soon, I realized this would exclude developers who don't have access to paid OpenAI services. Thanks to Elon Musk, you can get free API keys from Grok (<a href="https://x.ai/" target="_blank" rel="noopener noreferrer">https://x.ai/</a>).</p>
<p>However, I had to write separate code for each model provider. This is where the Vercel AI SDK shines. It provides a standardized interface for interacting with various LLM providers, allowing us to write code that works with multiple AI models. Whether you're using OpenAI, Anthropic's Claude, or other providers, the implementation remains consistent.</p>
<p>It provides a unified LanguageModel type, allowing you to specify any LLM model you wish to use. Simply check the environment to determine which model is available.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">LanguageModel</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">OPENAI_API_KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        model </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">openai</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'gpt-4-turbo'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">XAI_API_KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        model </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">xai</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'grok-beta'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token spread operator" style="color:#393A34">...</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The rest of the implementation uses the same unified API, regardless of which provider you choose.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/fab6fd46-0639-46b5-87cf-66964898b060" alt="vercel-ai-sdk" class="img_XvmG"></p>
<p>Here is the prompt we use to let AI generate the content of the doc:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> prompt </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    You are the expert of ZenStack open-source toolkit. </span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    You will generate a technical design document from a provided ZModel schema file that help developer understand the structure and behavior of the application. </span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    The document should include the following sections:</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    1. Overview </span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">        a. A short paragraph for the high-level description of this app</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">        b. Functionality</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    2. an array of model. Each model has below two information:</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">        a. model name</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">        b. array of access policies explained by plain text</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    here is the ZModel schema file:</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    \`\`\`zmodel</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">zmodel</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    \`\`\`</span><br></span><span class="token-line" style="color:#393A34"><span class="token template-string string" style="color:#e3116c">    </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="generating-structured-data">Generating structured data<a class="hash-link" aria-label="Direct link to Generating structured data" title="Direct link to Generating structured data" href="https://zenstack.dev/blog/code-as-doc#generating-structured-data">​</a></h2>
<p>When dealing with APIs, we prefer to use JSON data instead of plain text. Although many LLMs are capable of generating JSON, each has its own approach. For example, OpenAI provides a JSON mode, while Claude requires JSON formatting to be specified in the prompt. The good news is that Vercel SDK also unifies this ability across model providers using Zod schema.</p>
<p>For the prompt above, here is the corresponding response data structure we expect to receive.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> schema </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">object</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        overview</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">object</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            functionality</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        models</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">array</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">object</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                access_control_policies</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">array</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then call the <code>generateObject</code> API to let AI do his job:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> object </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateObject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        model</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        schema</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        prompt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Here is the returned type that allows you to work within a type-safe manner:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> object</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    overview</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        functionality</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    models</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        access_control_policies</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="generate-mermaid--erd-diagram">Generate Mermaid  ERD diagram<a class="hash-link" aria-label="Direct link to Generate Mermaid  ERD diagram" title="Direct link to Generate Mermaid  ERD diagram" href="https://zenstack.dev/blog/code-as-doc#generate-mermaid--erd-diagram">​</a></h2>
<p>Let's also generate the ERD diagram for each model. This part is quite straightforward and easy to implement, so I think writing code is more reliable and efficient here. Of course, you could still employ AI as a copilot here. 😄</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MermaidGenerator</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">generate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dataModel</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">DataModel</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> fields </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> dataModel</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isRelationshipField</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">reference</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">ref</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token function" style="color:#d73a49">isIdField</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'PK'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isForeignKeyField</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'FK'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">optional</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'"?"'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">' '</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">  </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> relations </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> dataModel</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isRelationshipField</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token comment" style="color:#999988;font-style:italic">// eslint-disable-next-line @typescript-eslint/no-non-null-assertion</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> oppositeModel </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">reference</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">ref</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token maybe-class-name">DataModel</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> oppositeField </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> oppositeModel</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">fields</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">find</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">reference</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">ref </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> dataModel</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token maybe-class-name">DataModelField</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> currentType </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> oppositeType </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> oppositeField</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> relation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">currentType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> oppositeType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token comment" style="color:#999988;font-style:italic">//many to many</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    relation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'}o--o{'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">currentType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">oppositeType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token comment" style="color:#999988;font-style:italic">//one to many</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    relation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'||--o{'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">currentType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> oppositeType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">array</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token comment" style="color:#999988;font-style:italic">//many to one</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    relation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'}o--||'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token comment" style="color:#999988;font-style:italic">//one to one</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    relation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> currentType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">optional</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'||--o|'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'|o--||'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">"</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">dataModel</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">"</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> relation</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">"</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">oppositeField</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">$container</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">": </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">' '</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'```mermaid'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'erDiagram'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">"</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">dataModel</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">" {\n</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">fields</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">\n}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> relations</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'```'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="stitch-everything-up">Stitch everything up<a class="hash-link" aria-label="Direct link to Stitch everything up" title="Direct link to Stitch everything up" href="https://zenstack.dev/blog/code-as-doc#stitch-everything-up">​</a></h2>
<p>Finally, we'll combine all the generated components together to get our final doc:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modelChapter </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> dataModels</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">### </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                mermaidGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">generate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">models</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">find</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> x</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">access_control_policies</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">- </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> content </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c"># Technical Design Document</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'&gt; Generated by [`ZenStack-markdown`](https://github.com/jiashengguo/zenstack-markdown)'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">object</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">overview</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">description</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">## Functionality</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">object</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">overview</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">functionality</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'## Models:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dataModels</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">- [</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">](#</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">x</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">)</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        modelChapter</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="off-the-shelf">Off the shelf<a class="hash-link" aria-label="Direct link to Off the shelf" title="Direct link to Off the shelf" href="https://zenstack.dev/blog/code-as-doc#off-the-shelf">​</a></h2>
<p>Of course, you don't have to implement this yourself. It's already published as an NPM package for you to install:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm i -D zenstack-markdown</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Add the plugin to your ZModel schema file</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">plugin zenstackmd {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    provider = 'zenstack-markdown'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Just don’t forget to put whatever AI API keys are available for you in your .env.  Otherwise, you might get some surprising results. 😉</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_KEY=xxxx</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">XAI_API_KEY=xxxxx</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ANTHROPIC_API_KEY=xxxx</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>ai</category>
            <category>vercel</category>
            <category>documentation</category>
        </item>
        <item>
            <title><![CDATA[When Embedded AuthN Meets Embedded AuthZ - Building Multi-Tenant Apps With Better-Auth and ZenStack]]></title>
            <link>https://zenstack.dev/blog/better-auth</link>
            <guid>https://zenstack.dev/blog/better-auth</guid>
            <pubDate>Sat, 14 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Better-auth is an emerging open-source TypeScript authentication framework that offers a comprehensive set of features and great extensibility. In this post, we'll explore how to integrate it with ZenStack to build a multi-tenant application with fine-grained access control.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-04f6c458706a0f1ad265763297474019.png" width="1792" height="1024" class="img_XvmG"></p>
<p>Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:</p>
<ul>
<li>Creating and managing tenants</li>
<li>User invitation flow</li>
<li>Managing roles and permissions</li>
<li>Enforcing data segregation and access control throughout the entire application</li>
</ul>
<p>It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.</p>
<p><a href="https://better-auth.com/" target="_blank" rel="noopener noreferrer">Better-auth</a> is an emerging open-source TypeScript authentication framework that offers a comprehensive set of features and great extensibility. Besides supporting a wide range of identity providers, its powerful plugin system allows you to add new features that contribute extensions across the entire stack - data model, backend API, and frontend hooks. A good example is the <a href="https://www.better-auth.com/docs/plugins/organization" target="_blank" rel="noopener noreferrer">Organization plugin</a>, which sets the foundation for implementing multi-tenant apps with access control.</p>
<p>While better-auth solves the problem of determining a user's identity and roles, <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> continues from there and uses such information to control what actions the user can perform on a piece of data. ZenStack is built above <a href="https://prisma.io/" target="_blank" rel="noopener noreferrer">Prisma ORM</a> and extends Prisma's power with flexible access control and automatic CRUD API. Since better-auth has built-in integration with Prisma, the two can make a perfect combination for building secure multi-tenant applications. This post will walk you through the steps of creating one.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-goal-and-the-stack">The goal and the stack<a class="hash-link" aria-label="Direct link to The goal and the stack" title="Direct link to The goal and the stack" href="https://zenstack.dev/blog/better-auth#the-goal-and-the-stack">​</a></h2>
<p>The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:</p>
<ul>
<li>
<p><strong>Organization management</strong></p>
<p>Users can create organizations and invite others to join. They can manage members and set their roles.</p>
</li>
<li>
<p><strong>Current context</strong></p>
<p>Users can choose an organization to be the active one.</p>
</li>
<li>
<p><strong>Data segregation</strong></p>
<p>Only data within the active organization can be accessed.</p>
</li>
<li>
<p><strong>Role-based access control</strong></p>
<ul>
<li>Admin members have full access to all data within their organization.</li>
<li>Regular members have full access to the todo lists they own.</li>
<li>Regular members can view the other members' todo lists and manage their content.</li>
</ul>
</li>
</ul>
<p>The essential weapons we'll use to build the app are:</p>
<ul>
<li><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a>: the full-stack framework</li>
<li><a href="https://better-auth.com/" target="_blank" rel="noopener noreferrer">Better-Auth</a>: user authentication and organization management</li>
<li><a href="https://prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a>: the ORM that we use to talk to the database</li>
<li><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a>: enhancing Prisma with access control and automatic CRUD API</li>
<li><a href="https://tanstack.com/query/latest" target="_blank" rel="noopener noreferrer">TanStack Query</a>: the data fetching/caching library</li>
</ul>
<p>The benefit of this stack is that everything runs embedded inside Next.js. There are no third-party cloud services or self-hosted ones. The only thing you need is a Next.js hoster and a database provider.</p>
<p>You can find the link to the completed project at the end of the post.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="base-setup">Base setup<a class="hash-link" aria-label="Direct link to Base setup" title="Direct link to Base setup" href="https://zenstack.dev/blog/better-auth#base-setup">​</a></h2>
<p>Better-auth's <a href="https://github.com/better-auth/better-auth/tree/main/demo/nextjs" target="_blank" rel="noopener noreferrer">Next.js Demo</a> provides a great starting point for us, which already includes:</p>
<ul>
<li>Authentication configuration</li>
<li>User sign-up and sign-in flow</li>
<li>Organization plugin for organization management and member invitation flow</li>
<li>Dashboard for self-serviced user management and organization management</li>
<li>Admin UI for managing users</li>
</ul>
<p>One major change that we'll make to the demo is switching from Kysely to Prisma as the database client.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/lib/auth.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> prismaAdapter </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'better-auth/adapters/prisma'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> auth </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">betterAuth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  appName</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Better Auth Demo'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  database</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">prismaAdapter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      provider</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'sqlite'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, install Prisma packages and generate a schema with the better-auth CLI:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install -D prisma</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npm install @prisma/client</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx @better-auth/cli generate</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The generated schema file should contain the following models:</p>
<ul>
<li><strong>User</strong>: a registered user</li>
<li><strong>Session</strong>: a user session</li>
<li><strong>Account</strong>: OAuth account (not used)</li>
<li><strong>Verification</strong>: sign-up verification record</li>
<li><strong>Organization</strong>: an organization</li>
<li><strong>Member</strong>: a member of an organization (join table between <code>User</code> and <code>Organization</code>)</li>
<li><strong>Invitation</strong>: an invitation to join an organization</li>
</ul>
<!-- -->
<p>The initial dashboard UI looks like:</p>
<p><img decoding="async" loading="lazy" alt="Initial UI" src="https://zenstack.dev/assets/images/initial-ui-0aa56d3cf2943d80d334d340766360f3.png" width="2060" height="1540" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="setting-up-zenstack">Setting up ZenStack<a class="hash-link" aria-label="Direct link to Setting up ZenStack" title="Direct link to Setting up ZenStack" href="https://zenstack.dev/blog/better-auth#setting-up-zenstack">​</a></h2>
<p>In the following sections, we'll use ZenStack to implement the access control requirements. ZenStack uses its own DSL called <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part1/zmodel">ZModel</a> to define data models and access policy rules. ZModel is a superset of the Prisma schema language. The ZenStack CLI can generate a Prisma schema from a ZModel file so that downstream Prisma consumers (like better-auth) will continue to work seamlessly.</p>
<p>Let's initialize the project with ZenStack:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack@latest init</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The command will install the necessary dependencies, and copies the "prisma/schema.prisma" file to "/schema.zmodel". Moving forward, we'll modify "schema.zmodel" and use the ZenStack CLI to regenerate the Prisma schema.</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="preparing-data-models">Preparing data models<a class="hash-link" aria-label="Direct link to Preparing data models" title="Direct link to Preparing data models" href="https://zenstack.dev/blog/better-auth#preparing-data-models">​</a></h2>
<p>Better-auth helped us generate the authentication-related data models, leaving us to work on the application-specific ones: Todo List and Todo. As mentioned previously, we should update "schema.zmodel" to define them:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id             </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">        </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt      </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  updatedAt      </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@updatedAt</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name           </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  owner</span><span class="token type-class-name">          User</span><span class="token plain">          </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  organization</span><span class="token type-class-name">   Organization</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">organizationId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  organizationId </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  todos</span><span class="token type-class-name">          Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">   </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  updatedAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@updatedAt</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  done      </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listId    </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  list</span><span class="token type-class-name">      TodoList</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<!-- -->
<div class="theme-admonition theme-admonition-info admonition_al3P alert alert--info"><div class="admonitionHeading_Kjwq"><span class="admonitionIcon_gHdU"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Why is the "organization" relation optional?</div><div class="admonitionContent_u1Ss"><p>Even with the "Organization" plugin enabled, our app still allows users to work without an active organization (personal mode). A <code>TodoList</code> created in personal mode won't have an <code>organization</code> associated with it.</p></div></div>
<p>Then regenerate Prisma schema and push changes to the database:</p>
<div class="language-base codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-base codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx prisma db push</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finally, create a "/lib/db.ts" file to export the Prisma client:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/lib/db.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> PrismaClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@prisma/client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PrismaClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="mounting-automatic-crud-api">Mounting automatic CRUD API<a class="hash-link" aria-label="Direct link to Mounting automatic CRUD API" title="Direct link to Mounting automatic CRUD API" href="https://zenstack.dev/blog/better-auth#mounting-automatic-crud-api">​</a></h2>
<p>ZenStack provides a Next.js server adapter that automatically exposes Prisma-style CRUD APIs. To mount it, install the server adapter package:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install @zenstackhq/server</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>, and then create a "/app/api/[...path]/router.ts" file:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/app/api/[...path]/router.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> prisma </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/lib/db'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NextRequestHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/server/next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NextRequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getPrisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useAppDir</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DELETE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PATCH</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">POST</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PUT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This will expose a set of CRUD endpoints like <code>/api/model/TodoList/findMany</code>, <code>/api/model/TodoList/create</code>, etc. You can find more details <a href="https://zenstack.dev/docs/2.x/reference/server-adapters/api-handlers/rpc" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Although we can call these APIs with <code>fetch</code> directly, a much easier way is to leverage ZenStack's <a href="https://zenstack.dev/docs/2.x/reference/plugins/tanstack-query" target="_blank" rel="noopener noreferrer">TanStack Query plugin</a> to generate client-side hooks.</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install @zenstackhq/tanstack-query</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">plugin</span><span class="token plain"> hooks </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@zenstackhq/tanstack-query"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  output </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./hooks/model"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You can then enjoy type-safe Prisma-style hooks in the frontend code.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> useFindManyTodoList </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">  </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/hooks/model'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">MyComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> lists</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyTodoList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> owner</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="implementing-access-control">Implementing access control<a class="hash-link" aria-label="Direct link to Implementing access control" title="Direct link to Implementing access control" href="https://zenstack.dev/blog/better-auth#implementing-access-control">​</a></h2>
<p>Now, we can manipulate the database from the frontend through the generated hooks and automatic API. However, the APIs are open to all without any protection, which is obviously not what we want.</p>
<p>The biggest value ZenStack adds above Prisma is access control, which can be implemented directly inside the schema using the <code>@@allow</code> and <code>@@deny</code> attributes. At runtime, ZenStack lets you create a wrapper around <code>PrismaClient</code> (called <em><strong>enhanced</strong></em> PrismaClient) that automatically enforces these policy rules. Access is rejected by default unless explicitly granted with an <code>@@allow</code> rule and not rejected by any <code>@@deny</code> rule. When using an enhanced client to access the database, inaccessible records are filtered out during read, and mutations with insufficient permissions are rejected.</p>
<p>In real-world applications, authorization is always connected to authentication: you'll determine a user's access based on his identity and other information (like organization membership, roles, etc.). In our context, we'll use better-auth to retrieve the current user's identity, active organization, and role in the organization and use this information as the "user context" when creating the enhanced <code>PrismaClient</code>. Since the auto APIs use the enhanced client, they are also secured.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/app/api/model/[...path]/route.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> reqHeaders </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">headers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> sessionResult </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">api</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getSession</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> reqHeaders</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">sessionResult</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// anonymous user, create enhanced client without user context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> organizationId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> organizationRole</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> session </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sessionResult</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">activeOrganizationId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// if there's an active orgId, get the role of the user in the org</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    organizationId </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">activeOrganizationId</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> org </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">api</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getFullOrganization</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> reqHeaders </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">org</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">members</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> myMember </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> org</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">members</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">find</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">m</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> m</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      organizationRole </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> myMember</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">role</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// create enhanced client with user context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> userContext </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    organizationId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    organizationRole</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userContext </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The user context will be accessible in ZModel policy rules via the special <code>auth()</code> function. To get it to work, we'll use a type to define the shape of <code>auth()</code>:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Auth </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  userId           </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  organizationId   </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  organizationRole </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@auth</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now, we're ready to write the policy rules. You can find more information about access policies <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part1/access-policy/" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="1-tenant-segregation">1. Tenant segregation<a class="hash-link" aria-label="Direct link to 1. Tenant segregation" title="Direct link to 1. Tenant segregation" href="https://zenstack.dev/blog/better-auth#1-tenant-segregation">​</a></h4>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// deny anonymous users</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// deny access to lists that don't belong to the user's active organization</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">organizationId </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> organizationId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="2-users-can-only-create-lists-for-themselves">2. Users can only create lists for themselves<a class="hash-link" aria-label="Direct link to 2. Users can only create lists for themselves" title="Direct link to 2. Users can only create lists for themselves" href="https://zenstack.dev/blog/better-auth#2-users-can-only-create-lists-for-themselves">​</a></h4>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// users can create lists for themselves</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> ownerId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="3-owner-and-admins-have-full-access">3. Owner and admins have full access<a class="hash-link" aria-label="Direct link to 3. Owner and admins have full access" title="Direct link to 3. Owner and admins have full access" href="https://zenstack.dev/blog/better-auth#3-owner-and-admins-have-full-access">​</a></h4>
<p>By default, better-auth's organization members can have "owner", "admin", or "member" role.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// full access to: list owner, org owner, and org admins</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">organizationRole </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'owner'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">organizationRole </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="4-readable-to-organization-members">4. Readable to organization members<a class="hash-link" aria-label="Direct link to 4. Readable to organization members" title="Direct link to 4. Readable to organization members" href="https://zenstack.dev/blog/better-auth#4-readable-to-organization-members">​</a></h4>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// if the list belongs to an org, it's readable to all members</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> organizationId </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="5-owner-and-organization-cannot-be-changed">5. Owner and organization cannot be changed<a class="hash-link" aria-label="Direct link to 5. Owner and organization cannot be changed" title="Direct link to 5. Owner and organization cannot be changed" href="https://zenstack.dev/blog/better-auth#5-owner-and-organization-cannot-be-changed">​</a></h4>
<p>You can use <code>@allow</code> and <code>@deny</code> attributes (note the single <code>@</code> sign) to define field-level rules.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> TodoList </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  organizationId </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_cNxG" id="6-a-user-as-full-access-to-todo-if-he-can-read-its-parent-todolist">6. A user as full access to <code>Todo</code> if he can read its parent <code>TodoList</code><a class="hash-link" aria-label="Direct link to 6-a-user-as-full-access-to-todo-if-he-can-read-its-parent-todolist" title="Direct link to 6-a-user-as-full-access-to-todo-if-he-can-read-its-parent-todolist" href="https://zenstack.dev/blog/better-auth#6-a-user-as-full-access-to-todo-if-he-can-read-its-parent-todolist">​</a></h4>
<p>We've managed to protect the <code>TodoList</code> model, and rules for the <code>Todo</code> model are yet to be defined. Fortunately, ZenStack allows you to reference relations in policy rules. The <code>check()</code> helper allows you to directly delegate permission check to a relation (here <code>Todo</code> -&gt; <code>TodoList</code>).</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `check()` delegates permission check to a relation</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-info admonition_al3P alert alert--info"><div class="admonitionHeading_Kjwq"><span class="admonitionIcon_gHdU"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_u1Ss"><p>Make sure to rerun <code>npx zenstack generate</code> after changing ZModel.</p></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="finally-the-todo-list-ui">Finally, the Todo list UI<a class="hash-link" aria-label="Direct link to Finally, the Todo list UI" title="Direct link to Finally, the Todo list UI" href="https://zenstack.dev/blog/better-auth#finally-the-todo-list-ui">​</a></h2>
<p>With the CRUD APIs secured and frontend hooks generated, implementing the UI for managing <code>TodoList</code>s becomes very straightforward. I'm only showing part of the implementation here.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/app/dashboard/todo-lists-card.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">TodoListsCard</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Note that you don't need to filter for the current user and the active organization</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// because the ZModel rules have taken care of it</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> todoLists </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyTodoList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    orderBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createdAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'desc'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> mutateAsync</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> del</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isPending</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> isDeleting </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useDeleteTodoList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onDelete</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">del</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Card</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CardHeader</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CardTitle</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Todo List</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">CardTitle</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">CardHeader</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CardContent</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">todoLists</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">list</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">createdAt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toLocaleString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">isDeleting</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript function" style="color:#d73a49">onDelete</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript" style="color:#00009f">list</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">                Delete</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">CardContent</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Card</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div align="center" style="width:100%"><iframe width="100%" height="600" src="https://www.youtube.com/embed/atfFZkypRN0?si=lbqKj4zlkx5quhVn" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe></div>
<p>You can find the fully completed code <a href="https://github.com/ymc9/better-auth-zenstack-multitenancy" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/better-auth#conclusion">​</a></h2>
<p>Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining better-auth and ZenStack. The end result is a secure application with great flexibility and little boilerplate code.</p>
<p>Better-auth also supports defining <a href="https://www.better-auth.com/docs/plugins/organization#custom-permissions" target="_blank" rel="noopener noreferrer">custom permissions</a> for organizations. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with better-auth's API and have ZenStack enforce them at runtime.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>auth</category>
            <category>better-auth</category>
            <category>multi-tenancy</category>
        </item>
        <item>
            <title><![CDATA[Building Multi-Tenant Apps Using StackAuth's "Teams" and Next.js]]></title>
            <link>https://zenstack.dev/blog/stackauth-multitenancy</link>
            <guid>https://zenstack.dev/blog/stackauth-multitenancy</guid>
            <pubDate>Sat, 07 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[StackAuth's "Teams" feature provides a powerful pre-built tenant management experience. Let's see how we can easily create a full-fledged multi-tenant application with it.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-6296b04a3d5fa34628bd180999f366de.png" width="1792" height="737" class="img_XvmG"></p>
<p>Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:</p>
<ul>
<li>Creating and managing tenants</li>
<li>User invitation flow</li>
<li>Managing roles and permissions</li>
<li>Enforcing data segregation and access control throughout the entire application</li>
</ul>
<p>It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.</p>
<p><a href="https://stack-auth.com/" target="_blank" rel="noopener noreferrer">StackAuth</a> is an open-source authentication and user management platform designed to integrate seamlessly into Next.js projects. Its combination of frontend/backend APIs and pre-built UI components dramatically simplifies the integration of such capabilities into your application. Similarly, its newer "Teams" feature provides an excellent starting point for creating multi-tenant applications. In this post, we'll explore leveraging it to build a non-trivial one while trying to keep our code simple and clean.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-goal-and-the-stack">The goal and the stack<a class="hash-link" aria-label="Direct link to The goal and the stack" title="Direct link to The goal and the stack" href="https://zenstack.dev/blog/stackauth-multitenancy#the-goal-and-the-stack">​</a></h2>
<p>The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:</p>
<ul>
<li>
<p><strong>Team management</strong></p>
<p>Users can create teams and invite others to join. They can manage members and set their roles.</p>
</li>
<li>
<p><strong>Current context</strong></p>
<p>Users can choose a team to be the current context.</p>
</li>
<li>
<p><strong>Data segregation</strong></p>
<p>Only data within the current team can be accessed.</p>
</li>
<li>
<p><strong>Role-based access control</strong></p>
<ul>
<li>Admin members have full access to all data within their team.</li>
<li>Regular members have full access to the todo lists they own.</li>
<li>Regular members can view the other members' todo lists and manage their content, as long as the list is not private.</li>
</ul>
</li>
</ul>
<p>The essential weapons we'll use to build the app are:</p>
<ul>
<li><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a>: the full-stack framework</li>
<li><a href="https://stack-auth.com/" target="_blank" rel="noopener noreferrer">StackAuth</a>: user authentication and team management</li>
<li><a href="https://prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a>: the ORM that we use to talk to the database</li>
<li><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a>: the authorization layer above Prisma that handles data segregation and access control</li>
</ul>
<p>You can find the link of the completed project at the end of the post.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="adding-team-management">Adding team management<a class="hash-link" aria-label="Direct link to Adding team management" title="Direct link to Adding team management" href="https://zenstack.dev/blog/stackauth-multitenancy#adding-team-management">​</a></h2>
<p>I assume you've created a Next.js project and completed the steps as described StackAuth's <a href="https://docs.stack-auth.com/getting-started/setup" target="_blank" rel="noopener noreferrer">setup guide</a>. Verify the basic sign-up/sign-in flow is working. Also, in StackAuth's management console, enable "Client-Side Team Creation" and "Automatic Team Creation" options in the "Team Settings" section.</p>
<p><img decoding="async" loading="lazy" alt="Team Settings" src="https://zenstack.dev/assets/images/team-settings-726711b9927ec0e46e062a75840519ce.png" width="2170" height="1044" class="img_XvmG"></p>
<p>Now, we can add the "SelectedTeamSwitcher" component into the layout.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/layout.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">SelectedTeamSwitcher</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@stackframe/stack"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RootLayout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">html</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">lang</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">en</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">StackProvider</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">app</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">stackServerApp</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">StackTheme</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">SelectedTeamSwitcher</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">main</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">children</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">main</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">StackTheme</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">StackProvider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">html</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With this one-liner, you'll have a set of fully working UI components for managing teams and choosing an active one!</p>
<p><img decoding="async" loading="lazy" alt="Team Management" src="https://zenstack.dev/assets/images/team-management-fa4bdb1cf9dadd761515581393c012d4.png" width="1898" height="1894" class="img_XvmG"></p>
<p>Although StackAuth made it effortless to add "teams" feature into an app, it's up to you to determine how to use the user and team information to control data access. We'll see how to connect it with Prisma/ZenStack to achieve proper authorization.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="setting-up-the-database">Setting up the database<a class="hash-link" aria-label="Direct link to Setting up the database" title="Direct link to Setting up the database" href="https://zenstack.dev/blog/stackauth-multitenancy#setting-up-the-database">​</a></h2>
<p>Our user and team data are stored on StackAuth's side. We need to store the todo lists and items in our own database. In this section, we'll set up Prisma and ZenStack and create the database schema.</p>
<p>Let's start with installing the necessary packages:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev prisma zenstack</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npm install @prisma/client @zenstackhq/runtime</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then we can create the database schema. Please note that we're creating a <strong>schema.zmodel</strong> file (as a replacement of "schema.prisma"). The <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part1/zmodel">ZModel language</a> is a superset of Prisma schema language, allowing you to model both the data schema and access control policies. In this section, we'll only focus on the data modeling part.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">datasource</span><span class="token plain"> db </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"postgresql"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  url      </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> env</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"DATABASE_URL"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">generator</span><span class="token plain"> js </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prisma-client-js"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Todo list</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">        </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  private   </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain">       </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  orgId     </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId   </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  todos</span><span class="token type-class-name">     Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Todo item</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id          </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title       </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  completedAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  list</span><span class="token type-class-name">        List</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">onDelete:</span><span class="token plain"> Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listId      </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You can then generate a regular Prisma schema file and push the schema to the database:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain"># The `zenstack generate` command generates the "prisma/schema.prisma" file and runs "prisma generate"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx prisma db push</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finally, create a "src/server/db.ts" file to export the Prisma client:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/server/db.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> PrismaClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@prisma/client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PrismaClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="implementing-access-control">Implementing access control<a class="hash-link" aria-label="Direct link to Implementing access control" title="Direct link to Implementing access control" href="https://zenstack.dev/blog/stackauth-multitenancy#implementing-access-control">​</a></h2>
<p>As mentioned, ZenStack allows you to model both data and access control in a single schema. Let's see how we can entirely implement our authorization requirements with it. The rules are defined with the <code>@@allow</code> and <code>@@deny</code> attributes. Access is rejected by default unless explicitly granted with an <code>@@allow</code> rule.</p>
<p>Although authorization is a distinct concept from authentication, it usually depends on authentication to work. For example, to determine if the current user has access to a list, a verdict must be made based on the user's id, current team, and role in the team. To access such information, let's first declare a type to express it:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// The shape of `auth()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Auth </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Current user's ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  userId         </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// User's current team ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  currentTeamId   </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// User's role in the current team</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  currentTeamRole </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@auth</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then you can use the special <code>auth()</code> function in access policy rules to access the current user's information. Let's use the <code>List</code> model as an example to demonstrate how the rules are defined.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// deny anonymous access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// tenant segregation: deny access if the user's current org doesn't match</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">currentOrgId </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> orgId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// owner/admin has full access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">currentOrgRole </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'org:admin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// can be read by org members if not private</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">private</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// when create, owner must be set to current user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The last piece of the puzzle is, as you may already be wondering, where the value of <code>auth()</code> comes from? At runtime, ZenStack offers an <code>enhance()</code> API to create an enhanced <code>PrismaClient</code> (a lightweighted wrapper) that automatically enforces the access policies. You pass in a user context (usually fetched from the authentication provider) when calling <code>enhance()</code>, and that context provides the value for <code>auth()</code>.</p>
<p>We'll see how it works in detail in the next section.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="finally-the-ui">Finally, the UI<a class="hash-link" aria-label="Direct link to Finally, the UI" title="Direct link to Finally, the UI" href="https://zenstack.dev/blog/stackauth-multitenancy#finally-the-ui">​</a></h2>
<p>Before diving into creating the UI, let's first make a helper to get an enhanced <code>PrismaClient</code> for the current user, team, and role.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/server/db.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> enhance </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@zenstackhq/runtime"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> stackServerApp </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"~/stack"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> stackAuthUser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> stackServerApp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> currentTeam </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> stackAuthUser</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">selectedTeam</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// by default StackAuth's team members have "admin" or "member" role</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> perm </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    currentTeam </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> stackAuthUser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getPermission</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">currentTeam</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> stackAuthUser</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        userId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> stackAuthUser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        currentTeamId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> stackAuthUser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">selectedTeam</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        currentTeamRole</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> perm </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"member"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// anonymous</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Let's build the UI using <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components" target="_blank" rel="noopener noreferrer">React Server Components</a> (RSC) and <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations" target="_blank" rel="noopener noreferrer">Server Actions</a>. We'll also consistently use the <code>getUserDb()</code> helper to access the database with access control enforcement.</p>
<p>Here's the RSC that renders the todo lists for the current user (with styling omitted):</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/components/TodoList.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Component showing Todo list for the current user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">TodoLists</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// enhanced PrismaClient automatically filters out</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// the lists that the user doesn't have access to</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> lists </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    orderBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> updatedAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"desc"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* client component for creating a new List */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CreateList</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">ul</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">lists</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Link</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript template-string string" style="color:#e3116c">/lists/</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token tag script language-javascript template-string interpolation" style="color:#00009f">list</span><span class="token tag script language-javascript template-string interpolation punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript template-string interpolation" style="color:#00009f">id</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">list</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">li</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">title</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">li</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Link</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">ul</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>A client component that creates a new list by calling into a server action:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/components/CreateList.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">"use client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> createList </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"~/app/actions"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">CreateList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onCreate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> title </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">prompt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Enter a title for your list"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">createList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">onCreate</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      Create a list</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/actions.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">'use server'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> revalidatePath </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"next/cache"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getUserDb </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"~/server/db"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> title </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">revalidatePath</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div align="center"><img src="https://zenstack.dev/assets/images/list-ui-00501e64b24328b5375230b4f9df9385.gif" width="640"></div>
<p>The components that manage Todo items are not shown for brevity, but the ideas are similar. You can find the fully completed code <a href="https://github.com/ymc9/stackauth-zenstack-multitenancy" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/stackauth-multitenancy#conclusion">​</a></h2>
<p>Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining StackAuth's "Teams" feature and ZenStack's access control capabilities. The end result is a secure application with great flexibility and little boilerplate code.</p>
<p>StackAuth also supports defining <a href="https://docs.stack-auth.com/concepts/permissions" target="_blank" rel="noopener noreferrer">custom permissions</a> for teams. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with StackAuth's dashboard and have ZenStack enforce them at runtime.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>auth</category>
            <category>stack-auth</category>
            <category>multi-tenancy</category>
        </item>
        <item>
            <title><![CDATA[Building Multi-Tenant Apps Using Clerk's "Organization" and Next.js]]></title>
            <link>https://zenstack.dev/blog/clerk-multitenancy</link>
            <guid>https://zenstack.dev/blog/clerk-multitenancy</guid>
            <pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Clerk's "organization" feature provides a powerful pre-built tenant management experience. Let's see how we can easily create a full-fledged multi-tenant application with it.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-32c5250e3a8feb99931e1475a73118cc.png" width="1792" height="710" class="img_XvmG"></p>
<p>Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:</p>
<ul>
<li>Creating and managing tenants</li>
<li>User invitation flow</li>
<li>Managing roles and permissions</li>
<li>Enforcing data segregation and access control throughout the entire application</li>
</ul>
<p>It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.</p>
<p><a href="https://clerk.com/" target="_blank" rel="noopener noreferrer">Clerk</a> is one of the most popular authentication and user management cloud services. Its combination of APIs and pre-built UI components dramatically simplifies the integration of such capabilities into your application. Similarly, its newer "Organization" feature provides an excellent starting point for creating multi-tenant applications. In this post, we'll explore leveraging it to build a non-trivial one while trying to keep our code simple and clean.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-goal-and-the-stack">The goal and the stack<a class="hash-link" aria-label="Direct link to The goal and the stack" title="Direct link to The goal and the stack" href="https://zenstack.dev/blog/clerk-multitenancy#the-goal-and-the-stack">​</a></h2>
<p>The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:</p>
<ul>
<li>
<p><strong>Organization management</strong></p>
<p>Users can create organizations and invite others to join. They can manage members and set their roles.</p>
</li>
<li>
<p><strong>Current context</strong></p>
<p>Users can choose an organization to be the current context.</p>
</li>
<li>
<p><strong>Data segregation</strong></p>
<p>Only data within the current organization can be accessed.</p>
</li>
<li>
<p><strong>Role-based access control</strong></p>
<ul>
<li>Admin members have full access to all data within their organization.</li>
<li>Regular members have full access to the todo lists they own.</li>
<li>Regular members can view the other members' todo lists and manage their content, as long as the list is not private.</li>
</ul>
</li>
</ul>
<p>Clerk can be used with any JavaScript framework, but its support for Next.js seems to be the best. So we'll use Next.js as our full-stack framework, along with two other essential pieces of weapon:</p>
<ul>
<li><a href="https://prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a>: the ORM</li>
<li><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a>: the access control layer on top of Prisma</li>
</ul>
<p>You can find the link of the completed project at the end of the post.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="adding-organization-management">Adding organization management<a class="hash-link" aria-label="Direct link to Adding organization management" title="Direct link to Adding organization management" href="https://zenstack.dev/blog/clerk-multitenancy#adding-organization-management">​</a></h2>
<p>I assume you've created a Next.js project and set up the basic Clerk sign-up/sign-in flow following <a href="https://clerk.com/docs/quickstarts/nextjs" target="_blank" rel="noopener noreferrer">the guide</a>. Also, make sure you've<a href="https://clerk.com/docs/organizations/overview" target="_blank" rel="noopener noreferrer"> enabled the "Organization" feature</a> in Clerk's dashboard.</p>
<p>Now, we can add the "OrganizationSwitcher" component into the layout.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/layout.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">OrganizationSwitcher</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@clerk/nextjs"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RootLayout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">ClerkProvider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">html</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">lang</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">en</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">SignedOut</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">SignInButton</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">SignedOut</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">SignedIn</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">OrganizationSwitcher</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">UserButton</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">SignedIn</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">html</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">ClerkProvider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With this one-liner, you'll have a set of fully working UI components for managing organizations and choosing an active one!</p>
<div align="center"><img src="https://zenstack.dev/assets/images/org-switcher-f436c734cfb87407a39fc48be0355b09.png" style="border-radius:15px" width="480"></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="setting-up-the-database">Setting up the database<a class="hash-link" aria-label="Direct link to Setting up the database" title="Direct link to Setting up the database" href="https://zenstack.dev/blog/clerk-multitenancy#setting-up-the-database">​</a></h2>
<p>Our user and organization data are stored on Clerk's side. We need to store the todo lists and items in our own database. In this section, we'll set up Prisma and ZenStack and create the database schema.</p>
<p>Let's start with installing the necessary packages:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev prisma zenstack</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npm install @prisma/client @zenstackhq/runtime</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then we can create the database schema. Please note that we're creating a <strong>schema.zmodel</strong> file (as a replacement of "schema.prisma"). The <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part1/zmodel">ZModel language</a> is a superset of Prisma schema language, allowing you to model both the data schema and access control policies. In this section, we'll only focus on the data modeling part.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">datasource</span><span class="token plain"> db </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"postgresql"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  url      </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> env</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"DATABASE_URL"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">generator</span><span class="token plain"> js </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prisma-client-js"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Todo list</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">        </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  private   </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain">       </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  orgId     </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId   </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  todos</span><span class="token type-class-name">     Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Todo item</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id          </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title       </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  completedAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  list</span><span class="token type-class-name">        List</span><span class="token plain">      </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">onDelete:</span><span class="token plain"> Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listId      </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You can then generate a regular Prisma schema file and push the schema to the database:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain"># The `zenstack generate` command generates the "prisma/schema.prisma" file and runs "prisma generate"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx prisma db push</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finally, create a "src/server/db.ts" file to export the Prisma client:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/server/db.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> PrismaClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@prisma/client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PrismaClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="implementing-access-control">Implementing access control<a class="hash-link" aria-label="Direct link to Implementing access control" title="Direct link to Implementing access control" href="https://zenstack.dev/blog/clerk-multitenancy#implementing-access-control">​</a></h2>
<p>As mentioned, ZenStack allows you to model both data and access control in a single schema. Let's see how we can entirely implement our authorization requirements with it. The rules are defined with the <code>@@allow</code> and <code>@@deny</code> attributes. Access is rejected by default unless explicitly granted with an <code>@@allow</code> rule.</p>
<p>Although authorization is a distinct concept from authentication, it usually depends on authentication to work. For example, to determine if the current user has access to a list, a verdict must be made based on the user's id, current organization, and role in the organization. To access such information, let's first declare a type to express it:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// The shape of `auth()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Auth </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Current user's ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  userId         </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// User's current organization ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  currentOrgId   </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// User's role in the current organization</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  currentOrgRole</span><span class="token type-class-name"> Role</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@auth</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then you can use the special <code>auth()</code> function in access policy rules to access the current user's information. Let's use the <code>List</code> model as an example to demonstrate how the rules are defined.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">/schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// deny anonymous access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// tenant segregation: deny access if the user's current org doesn't match</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">currentOrgId </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> orgId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// owner/admin has full access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">currentOrgRole </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'org:admin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// can be read by org members if not private</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">private</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// when create, owner must be set to current user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ownerId </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The last piece of the puzzle is, as you may already be wondering, where the value of <code>auth()</code> comes from? At runtime, ZenStack offers an <code>enhance()</code> API to create an enhanced <code>PrismaClient</code> (a lightweighted wrapper) that automatically enforces the access policies. You pass in a user context (usually fetched from the authentication provider) when calling <code>enhance()</code>, and that context provides the value for <code>auth()</code>.</p>
<p>We'll see how it works in detail in the next section.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="finally-the-ui">Finally, the UI<a class="hash-link" aria-label="Direct link to Finally, the UI" title="Direct link to Finally, the UI" href="https://zenstack.dev/blog/clerk-multitenancy#finally-the-ui">​</a></h2>
<p>Before diving into creating the UI, let's first make a helper to get an enhanced <code>PrismaClient</code> for the current user.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/server/db.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> auth </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@clerk/nextjs/server"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Role </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@prisma/client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> enhance </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@zenstackhq/runtime"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// get the current user's information from Clerk</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> orgId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> orgRole </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// create an enhanced Prisma Client with proper user context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> userId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        userId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        currentOrgId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> orgId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        currentOrgRole</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> orgRole</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// anonymous</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Let's build the UI using <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components" target="_blank" rel="noopener noreferrer">React Server Components</a> (RSC) and <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations" target="_blank" rel="noopener noreferrer">Server Actions</a>. We'll also consistently use the <code>getUserDb()</code> helper to access the database with access control enforcement.</p>
<p>Here's the RSC that renders the todo lists for the current user (with styling omitted):</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/components/TodoList.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Component showing Todo list for the current user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">TodoLists</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// enhanced PrismaClient automatically filters out</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// the lists that the user doesn't have access to</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> lists </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    orderBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> updatedAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"desc"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* client component for creating a new List */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CreateList</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">ul</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">lists</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Link</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript template-string string" style="color:#e3116c">/lists/</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token tag script language-javascript template-string interpolation" style="color:#00009f">list</span><span class="token tag script language-javascript template-string interpolation punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript template-string interpolation" style="color:#00009f">id</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">list</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">li</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">title</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">li</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Link</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">ul</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>A client component that creates a new list by calling into a server action:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/components/CreateList.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">"use client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> createList </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"~/app/actions"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">CreateList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onCreate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> title </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">prompt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Enter a title for your list"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">createList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">onCreate</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      Create a list</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/actions.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">'use server'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> revalidatePath </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"next/cache"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getUserDb </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"~/server/db"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUserDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> title </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">revalidatePath</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div align="center"><img src="https://zenstack.dev/assets/images/list-ui-8ddd5ed7cc382c8220c98f1bc30cac32.gif" style="border-radius:15px" width="640"></div>
<p>The components that manage Todo items are not shown for brevity, but the ideas are similar. You can find the fully completed code <a href="https://github.com/ymc9/clerk-zenstack-multitenancy" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/clerk-multitenancy#conclusion">​</a></h2>
<p>Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining Clerk's "Organization" feature and ZenStack's access control capabilities. The end result is a secure application with great flexibility and little boilerplate code.</p>
<p>Clerk also supports defining <a href="https://clerk.com/docs/organizations/roles-permissions" target="_blank" rel="noopener noreferrer">custom roles and permissions</a> (still Beta) for organizations. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with Clerk's dashboard and have ZenStack enforce them at runtime.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>auth</category>
            <category>clerk</category>
            <category>multi-tenancy</category>
        </item>
        <item>
            <title><![CDATA[Typing Those JSON Fields? Yes, You Can!]]></title>
            <link>https://zenstack.dev/blog/json-typing</link>
            <guid>https://zenstack.dev/blog/json-typing</guid>
            <pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This post introduces the new strongly typed JSON field feature in ZenStack.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-3eda95b7a78f67f35c0e1d05f6d2c1bc.png" width="1478" height="596" class="img_XvmG"></p>
<p>SQL databases provide us with many benefits, the most important of which is strong schema enforcement. Yes, you pay the cost of migration when the schema changes, but the gain is far more significant - your code is clean because it can assume all data are in correct shapes.</p>
<p>However, once in a while, we want to break free from such strong guarantees for valid reasons. You may have some tiny objects that you want to attach to the main entities (e.g., metadata of an image) without formalizing them into a separate table. Or you need to store records with many possible sparse fields but want to avoid creating wide tables.</p>
<p>Prisma's JSON type provides a generic escape hatch for such scenarios. It allows storing arbitrary data and gives you a generic&nbsp;<code>JsonValue</code>&nbsp;type in the query results.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.prisma</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Image </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  metadata </span><span class="token entity" style="color:#36acaa">Json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">Metadata</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  width</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  height</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  format</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findFirstOrThrow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// an explicit cast into the desired type</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token class-name keyword" style="color:#00009f">const</span><span class="token plain"> metadata </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">metadata </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> Metadata</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Image dimensions:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">width</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'by'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">height</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is not always ideal because, in practice, many people use JSON type in a "controlled" way - only data of specific fixed shapes are stored in a field. So, regaining some of the strong typing capabilities would be very beneficial.</p>
<p>ZenStack's new "strongly typed JSON field" feature is designed to address this need. It allows you to define shapes of JSON data in the schema, and "fixes" <code>PrismaClient</code> to return data with correct types. The feature is in preview and only supports PostgreSQL for now.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="using-strongly-typed-json-fields">Using strongly typed JSON fields<a class="hash-link" aria-label="Direct link to Using strongly typed JSON fields" title="Direct link to Using strongly typed JSON fields" href="https://zenstack.dev/blog/json-typing#using-strongly-typed-json-fields">​</a></h2>
<p>The first step is to use the new <code>type</code> keyword to define the shape of the JSON data in the ZModel schema:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Metadata </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  width </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  height </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  format </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Image </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  metadata</span><span class="token type-class-name"> Metadata</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Types have a structure similar to models but are not mapped to database tables. They only exist for typing and validation purposes. You can not have relations to other models in types. However, you can include fields of other types to form a nested structure.</p>
<p>When you run <code>zenstack generate</code>, the compiler will transform the typed JSON fields back into the regular Prisma <code>Json</code> type:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.prisma</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Image </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">autoincrement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  metadata </span><span class="token entity" style="color:#36acaa">Json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>So, where did the <code>Metadata</code> type go? It's compiled into a TypeScript type declaration, which is used to type the query results when you use the ZenStack-enhanced <code>PrismaClient</code>:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> enhance </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findFirstOrThrow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// image.metadata is now directly typed as { width: number, height: number, format: string }</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Image dimensions:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">metadata</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">width</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'by'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">metadata</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">height</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>When you create or update, the input is also properly typed so you get nice auto-completion and typechecking for the payload:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      width</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1920</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      height</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'1080'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// &lt;- type error here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      format</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jpeg'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Straightforward, isn't it? But the feature doesn't stop here.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="how-about-some-runtime-validation">How about some runtime validation?<a class="hash-link" aria-label="Direct link to How about some runtime validation?" title="Direct link to How about some runtime validation?" href="https://zenstack.dev/blog/json-typing#how-about-some-runtime-validation">​</a></h2>
<p>For mutations, ZenStack also validates the input data's shape at runtime by deriving a Zod schema from the type declaration. You can also add additional constraints to fields the same way you can do with models:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> Metadata </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  width </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@gt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@lt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  height </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@gt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@lt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  format </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Mutation calls violating these constraints will be rejected:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      width</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1920</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      height</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10800</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// &lt;- runtime error here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      format</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jpeg'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-plain codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-plain codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">Error calling enhanced Prisma method `image.create`: denied by policy: image entities failed 'create' check, </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">input failed validation: Validation error: Number must be less than 10000 at "metadata.height"</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="is-it-really-type-safe">Is it really type-safe?<a class="hash-link" aria-label="Direct link to Is it really type-safe?" title="Direct link to Is it really type-safe?" href="https://zenstack.dev/blog/json-typing#is-it-really-type-safe">​</a></h2>
<p>JSON fields are meant to hold arbitrary data types, so there isn't really a way to guarantee data consistency. As such, to preserve enough flexibility, ZenStack doesn't validate if the query results comply with the type declaration. This effectively means you can't trust the TypeScript typings alone if you know the column contains mixed data.</p>
<p>One way to mitigate the problem is to validate the data with the generated Zod schemas explicitly:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> MetadataSchema </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime/zod/models'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findFirstOrThrow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> metadata </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> MetadataSchema</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">metadata</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="next-steps">Next steps<a class="hash-link" aria-label="Direct link to Next steps" title="Direct link to Next steps" href="https://zenstack.dev/blog/json-typing#next-steps">​</a></h2>
<p>One area that's not addressed by this feature yet is the filtering part. The <code>where</code> clause still follows Prisma's <a href="https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-a-json-field-simple" target="_blank" rel="noopener noreferrer">Json filter format</a>:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// find images with width greater than 102</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'width'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> gt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>We can potentially "enhance" that part to provide a typed experience like:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">main.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> width</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> gt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Is it useful, or can it be confusing (as it looks the same as relation filters)? Let us know by leaving a comment below. You can also learn more about this feature in the <a href="https://zenstack.dev/docs/2.x/guides/typing-json" target="_blank" rel="noopener noreferrer">official guide</a>.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>prisma</category>
        </item>
        <item>
            <title><![CDATA[Programmers, Will AI Work For You, With You, or Without You?]]></title>
            <link>https://zenstack.dev/blog/ai-programmer</link>
            <guid>https://zenstack.dev/blog/ai-programmer</guid>
            <pubDate>Mon, 14 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A thought experiment on the roles AI can play in software development.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-65888595058e0dd15094fb2514c95547.png" width="1792" height="1024" class="img_XvmG"></p>
<blockquote>
<p><strong>"Programming is dead."</strong></p>
</blockquote>
<p>A friend said so when he heard I was working on dev tools. It was right after LLM’s coding abilities shocked the world. He felt it pointless to continue building tools for human programmers anymore when AI is taking over this profession. You'll never be wrong by predicting without attaching a time frame. Dead when? At least for the time being, the emergence of generative AI has introduced more work for programmers, some more difficult than before.</p>
<p>Whether AI will replace human developers is too big a topic to tackle. However, we can keep an open mind and explore different roles that AI can play in software development. Let's conduct thought experiments to imagine what each means to us.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="coding-copilot">Coding Copilot<a class="hash-link" aria-label="Direct link to Coding Copilot" title="Direct link to Coding Copilot" href="https://zenstack.dev/blog/ai-programmer#coding-copilot">​</a></h2>
<p>We all love GitHub Copilot. There’s absolutely zero learning curve. Your IDE suddenly gets 10x smarter and generates code snippet completions as you type. It’s fast. It’s context-aware. It keeps you in the flow by letting you stay inside the IDE.</p>
<p>The quality is pretty good. Far from perfect, but the tolerance in this scenario is also pretty high. Who doesn’t enjoy some free code written for? Even if it’s not 100% correct, it can still be a good starting point. Otherwise, you’ll go to StackOverflow and grab something there anyway, won’t you?</p>
<p>A Copilot works <strong>FOR</strong> developers. It’s a tool, but a very smart one. It’s fully at your disposal, and its entire goal is to make you more productive. You, the human programmer, are the pilot. This brings several implications:</p>
<ol>
<li>
<p>All prior wisdom generated by humans for humans continues to be valid and important — writing readable code, using design patterns to combat complexity, planning for the future, etc. Eventually, they are used to overcome humans' limited capacity for comprehension, memory, and consistency.</p>
</li>
<li>
<p>Languages, frameworks, and libraries will continue to be created and evolve as they are today. Made by humans for humans, these tools are the actualization of wisdom mentioned in #1. Contrary to what laymen may expect, the top priority of these tools is not generating powerful and performant applications but lowering programmers' mental load. DX will continue to be one of the most essential topics in the industry.</p>
</li>
<li>
<p>Following #1 and #2, such AI Copilot must adapt well to whatever tools the human programmer has chosen to use. It must serve him by creating code that not only works but is also "good" by human standards. Preferably, the code should have a consistent style as those manually written.</p>
</li>
</ol>
<p>Copilots like GitHub Copilot, Codeium, and Cursor have proven very useful. However, they are certainly not the kind of disruptive innovation the crowd anticipates.</p>
<p>What's next?</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="buddy-developer">Buddy Developer<a class="hash-link" aria-label="Direct link to Buddy Developer" title="Direct link to Buddy Developer" href="https://zenstack.dev/blog/ai-programmer#buddy-developer">​</a></h2>
<p>Although routines are the primary unit developers organize and work on their code, they usually think at a higher granularity — feature. Features have business meanings, making them a good fit for planning, communication, and delivery. They’re also the ideal unit for breaking work down and distributing it among team members.</p>
<p>Naturally, AI's more ambitious goal is to tackle feature development directly.</p>
<p>You play a product manager or team leader role in such a setup. The AI works <strong>WITH</strong> you. It takes requirements from you, employs its own thoughts to generate an implementation strategy, and eventually comes up with code that contains the feature and integrates seamlessly into the project's code base. Instead of prompting with code or comments, as when you use Copilot, you prompt your AI buddy with requirements in natural language, optionally complemented by other high-level artifacts like a flow chart.</p>
<p>This poses some very tough challenges. One obvious problem is that humans are terribly incapable of describing things precisely.</p>
<blockquote>
<p>Wife: buy a watermelon on your way home. If you see tomatoes, buy two.</p>
<p>Q: how many watermelons should the husband buy if he sees tomatoes?</p>
</blockquote>
<p>Humans are even worse at precisely understanding other people’s thoughts. So when we work with our AI buddy, we can have these desperate struggles:</p>
<ul>
<li>Have I explained enough of my needs to the AI?</li>
<li>Does the execution plan generated by the AI (most AIs take a "plan-then-write" approach) show that it’ll actually cover everything I need?</li>
</ul>
<p>Of course, the ultimate guarantee is to review every line of code the AI writes thoroughly, but our expectations should be higher than that. When humans see ambiguity, we resort to common sense. If a pair of communicators share a "common" common sense, they’re likely to be on the same page. That's why a team gets more efficient after working together for an extended period of time. LLMs are already amazingly good at capturing common sense (albeit with a brute-force approach). As they get even better in the future, we can probably have enough confidence that we’re actually talking to a reasonable person.</p>
<p>Another bigger question is whether LLMs are powerful enough to implement features. Today’s LLMs have extremely tight context size limits. Unlike Copilots, which work at the code snippet level, feature-building AIs need a ton of context, often a big portion of the entire code base, and maybe access the code change history to understand why something currently works the way it does. Also, generating large chunks of code is computation-intensive, which can be rather slow and prohibitively expensive.</p>
<p>Of course, we can wait for LLMs to get much stronger or employ techniques like CoT to mitigate the problem, but another parallel solution is probably to adapt our project setup to help AIs do a better job. Use more high-level programming languages (including DSLs), do more declarative coding, make APIs more succinct, and employ more pre-built components so that AIs don’t need to generate from scratch. Basically, just try programming at a higher abstraction level and write less code. Just think about it: how much difference will it make if AI codes against a well-designed ORM API vs. generating SQL directly to access the database? A higher level of abstraction reduces AI’s cognition and generation load, and thus less chance of hallucination.</p>
<p>The benefits are also mutual. If AI generates higher-level code, you’ll also have a much easier time comprehending it, validating it, and making changes to it as necessary.</p>
<p>Feature-building AI is still in its very early stages. Products like <a href="https://youtu.be/V9_RzjqCXP8" target="_blank" rel="noopener noreferrer">Cursor Composer</a>, <a href="https://www.marblism.com/" target="_blank" rel="noopener noreferrer">Marblism</a>, and <a href="https://docs.replit.com/replitai/agent" target="_blank" rel="noopener noreferrer">Replit Agent</a> are having a good head start. For such products to succeed, a solid long-term partnership needs to form between humans and AI. It requires finding the right compromise (languages, frameworks, libraries, etc.) that allows both humans and AI to work effectively.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="fully-autonomous-software-engineer">Fully Autonomous Software Engineer<a class="hash-link" aria-label="Direct link to Fully Autonomous Software Engineer" title="Direct link to Fully Autonomous Software Engineer" href="https://zenstack.dev/blog/ai-programmer#fully-autonomous-software-engineer">​</a></h2>
<blockquote>
<p>We even tried giving Devin real jobs on Upwork and it could do those too!
— <a href="https://www.cognition.ai/blog/introducing-devin#:~:text=We%20even%20tried%20giving%20Devin%20real%20jobs%20on%20Upwork%20and%20it%20could%20do%20those%20too!" target="_blank" rel="noopener noreferrer">Introducing Devin, Cognition AI</a></p>
</blockquote>
<p>I’m not sure how true the story is, as it mentioned no details about what kind of jobs were done, how many times they were done, the success rate, how satisfied the customers were, etc.</p>
<p>The ultimate form of AI-assisted programming will be fully autonomous programming agents. It requires no supervision from professional software engineers, takes requirements directly from non-technical stakeholders, and delivers systems end to end. From a developer’s perspective, the AI works <strong>WITHOUT</strong> us! Or, in more brutal words, it replaces us. Instead of hiring an IT team, a non-tech-savvy company may purchase AI computation power to build and host the business applications they need.</p>
<p>It can have some very interesting implications:</p>
<ol>
<li>
<p>How the application is built can be a complete black box. This is what happens inside an organization when the IT department delivers an app to a business team. The demanding party doesn’t care and can’t understand how it works internally.</p>
</li>
<li>
<p>Given #1, AI can implement the system as straightforwardly as it sees fit. There’s no more restraint on writing code that humans can understand and change. There’s no need to follow best practices designed to mitigate human weakness. Duplicated code? Not a problem as long as it makes sure when a change is needed, all duplicates are consistently updated. SOLID principles? No more relevant. What humans see as shitty code can be perfectly fine for AI.</p>
</li>
</ol>
<p>If we pursue this direction, it'll make sense to design languages and building blocks that maximize LLM's performance. AI programming and human programming would become two distinct worlds. Is it utopia or dystopia? I’m not sure. Fortunately, after a two-year cooling down, we know today’s AI is still extremely limited. A full-scale replacement of human developers is not coming any time soon.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="final-thoughts">Final Thoughts<a class="hash-link" aria-label="Direct link to Final Thoughts" title="Direct link to Final Thoughts" href="https://zenstack.dev/blog/ai-programmer#final-thoughts">​</a></h2>
<p>The future is very uncertain. But what’s certain is that software developers are among the first cohort to benefit from AI. We will also be the first group of people who struggle with it. Anyone who seriously integrated LLM into a product knows how frustrating it is to program against it — it’s restricted, unstable, slow, and hallucinating all the time. It's an API that requires us to think very differently. If old-days APIs are like Newtonian physics, LLMs are quantum mechanics — no more determinism, everything is probabilistic.</p>
<p>It’s too early to worry about being replaced by AI programmers. What’s imminent is to learn how to combat its weakness. Regardless of its immaturity, Generative AI is undoubtedly the most promising key to unlocking an exciting future.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>ai</category>
            <category>programming</category>
        </item>
        <item>
            <title><![CDATA[Supabase RLS Alternative]]></title>
            <link>https://zenstack.dev/blog/supabase-alternative</link>
            <guid>https://zenstack.dev/blog/supabase-alternative</guid>
            <pubDate>Wed, 24 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Show the limitation of Supabase RLS(Row Level Security) with a multi-tenancy SaaS example and introduce ZenStack as an alternative.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-151d0f0e2117ecc4154fd9603de23713.png" width="1912" height="1072" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-short-history-of-baas">A Short History of BaaS<a class="hash-link" aria-label="Direct link to A Short History of BaaS" title="Direct link to A Short History of BaaS" href="https://zenstack.dev/blog/supabase-alternative#a-short-history-of-baas">​</a></h2>
<p>In the early days of web and mobile app development, building a backend from scratch was laborious and error-prone. Developers had to manage servers, databases, and infrastructure and ensure scalability while writing the core business logic of their applications.  Then came BaaS(Backend-as-a-Service), promising to liberate developers from this burden.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="firebase-the-pioneer">Firebase: The Pioneer<a class="hash-link" aria-label="Direct link to Firebase: The Pioneer" title="Direct link to Firebase: The Pioneer" href="https://zenstack.dev/blog/supabase-alternative#firebase-the-pioneer">​</a></h3>
<p>Firebase was one of the first BaaS platforms to gain widespread adoption. It was acquired by Google, and at Google I/O in <strong>2016</strong>, it announced an expansion of its services to become a unified BaaS platform for mobile developers. Firebase quickly became popular among developers for its ease of use and integration with Google's ecosystem.</p>
<p>However, as projects grew in complexity,  so did concerns about vendor lock-in and data control.  Its rigid data models and scalability issues led developers to seek more flexible and robust alternatives.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="supabase-the-open-source-contender">Supabase: The Open-Source Contender<a class="hash-link" aria-label="Direct link to Supabase: The Open-Source Contender" title="Direct link to Supabase: The Open-Source Contender" href="https://zenstack.dev/blog/supabase-alternative#supabase-the-open-source-contender">​</a></h3>
<p>In response to these limitations, Supabase was founded in <strong>2020</strong>, positioning itself as the “open-source Firebase alternative.” Built on top of PostgreSQL, it offered a more flexible and powerful database solution while remaining open-source. Unsurprisingly, it soon became the new spokesperson for BaaS.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="aclaccess-control-layer-is-the-core">ACL(Access Control Layer) is the Core<a class="hash-link" aria-label="Direct link to ACL(Access Control Layer) is the Core" title="Direct link to ACL(Access Control Layer) is the Core" href="https://zenstack.dev/blog/supabase-alternative#aclaccess-control-layer-is-the-core">​</a></h2>
<p>While BaaS promises a simple abstraction of connecting the frontend to the database,  it actually rejects another old-school promise:</p>
<p><strong>You never expose the database directly to the frontend</strong></p>
<p>The hero behind it is the ACL, or more specifically, authorization. It is the gatekeeper that stands between your database and potentially malicious actors. It ensures that users can only read or write data they are authorized to access.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="firebase-security-rules">Firebase: Security Rules<a class="hash-link" aria-label="Direct link to Firebase: Security Rules" title="Direct link to Firebase: Security Rules" href="https://zenstack.dev/blog/supabase-alternative#firebase-security-rules">​</a></h3>
<p>Firebase introduces a simple DSL to enforce access control.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">service cloud</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">firestore</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  match </span><span class="token operator" style="color:#393A34">/</span><span class="token plain">databases</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">database</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">documents </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    match </span><span class="token operator" style="color:#393A34">/</span><span class="token plain">posts</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">postId</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      allow read</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      allow write</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">auth</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">uid</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> resource</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authorId</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="supabase-rlsrow-level-security">Supabase: RLS(Row Level Security)<a class="hash-link" aria-label="Direct link to Supabase: RLS(Row Level Security)" title="Direct link to Supabase: RLS(Row Level Security)" href="https://zenstack.dev/blog/supabase-alternative#supabase-rlsrow-level-security">​</a></h3>
<p>Since Supabase is built upon Postgres,  it could leverage PostgreSQL's robust RLS to handle access control.</p>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">-- owner has full access to her own posts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">CREATE</span><span class="token plain"> POLICY post_owner_policy </span><span class="token keyword" style="color:#00009f">ON</span><span class="token plain"> post</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">USING</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">owner </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">current_user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Since RLS is written in SQL, it allows more fine-grained access control. Developers can define policies that determine which rows of data a user can access based on their role or other attributes.  Theoretically, it could express any authorization pattern you use, like RBAC, ABAC, PBAC, etc.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="multi-tenancy-saas-example">Multi-Tenancy SaaS Example<a class="hash-link" aria-label="Direct link to Multi-Tenancy SaaS Example" title="Direct link to Multi-Tenancy SaaS Example" href="https://zenstack.dev/blog/supabase-alternative#multi-tenancy-saas-example">​</a></h2>
<p>Multi-tenancy is the classical pattern used in SaaS applications. An application can host many organizations, and users can join organizations and access resources based on their permissions. Let’s use a ToDo app to illustrate.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="database-model">Database Model<a class="hash-link" aria-label="Direct link to Database Model" title="Direct link to Database Model" href="https://zenstack.dev/blog/supabase-alternative#database-model">​</a></h3>
<!-- -->
<ul>
<li>The <code>User</code> and <code>Space</code> are many-to-many relations through the relation table <code>SpaceUser</code>.</li>
<li>A <code>Todo</code> belongs to a <code>User</code>, and a <code>List</code></li>
<li>A <code>List</code> belongs to the <code>User</code>, and a <code>Space</code></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="rls-rules-for-list">RLS rules for List<a class="hash-link" aria-label="Direct link to RLS rules for List" title="Direct link to RLS rules for List" href="https://zenstack.dev/blog/supabase-alternative#rls-rules-for-list">​</a></h3>
<p>Let’s go through the access control requirements for <code>List</code> model and the corresponding RLS rules.</p>
<ul>
<li>
<p>Create</p>
<ul>
<li>owner must be set to the current user.</li>
<li>user must be in the space.</li>
</ul>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">create</span><span class="token plain"> policy </span><span class="token string" style="color:#e3116c">"list_create"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">on</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"public"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">to</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">check</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">EXISTS</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"userId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>Read</p>
<ul>
<li>can be read by the owner.</li>
<li>can be read by space members if not private.</li>
</ul>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">create</span><span class="token plain"> policy </span><span class="token string" style="color:#e3116c">"list_read"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">on</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"public"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">to</span><span class="token plain"> authenticated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">using</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">OR</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">NOT</span><span class="token plain"> private</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">EXISTS</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"userId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>Update</p>
<ul>
<li>only the owner is allowed to update</li>
<li>owner must be in the space of the current list</li>
<li>it doesn’t allow to change owner</li>
</ul>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">create</span><span class="token plain"> policy </span><span class="token string" style="color:#e3116c">"list_update"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">on</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"public"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">to</span><span class="token plain"> authenticated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">using</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">EXISTS</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"userId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">check</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>Delete</p>
<ul>
<li>can be deleted by owner</li>
</ul>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">create</span><span class="token plain"> policy </span><span class="token string" style="color:#e3116c">"list.delete"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">on</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"public"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">to</span><span class="token plain"> authenticated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">using</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ul>
<p>Supabase provides a RESTful API using <a href="https://postgrest.org/" target="_blank" rel="noopener noreferrer">PostgREST</a>. However, without RLS, you will expose your database to the frontend. With the RLS policies created above, it’s safe to expose the API to the public because each user can only access the data allowed by the policy. For example, if you try to get all the <code>List</code> items using the API below, you will only receive the ones you are allowed to read by the read policy:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">curl </span><span class="token string" style="color:#e3116c">'{SUPABASE_PROJECT_URL}/rest/v1/List?select=*'</span><span class="token plain"> \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token constant" style="color:#36acaa">H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"apikey: SUPABASE_ANON_KEY"</span><span class="token plain"> \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token constant" style="color:#36acaa">H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Authorization: Bearer USER_JWT_TOKEN"</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Everything looks perfect, especially if you are familiar with SQL. So why do I need alternatives?</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/08ac420c-4f2b-4147-b4b5-a6c766ea61a3" alt="perfect-meme" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-problems-of-supabase-rls">The Problems of Supabase RLS<a class="hash-link" aria-label="Direct link to The Problems of Supabase RLS" title="Direct link to The Problems of Supabase RLS" href="https://zenstack.dev/blog/supabase-alternative#the-problems-of-supabase-rls">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-separation-from-application-logic">1. Separation from Application Logic<a class="hash-link" aria-label="Direct link to 1. Separation from Application Logic" title="Direct link to 1. Separation from Application Logic" href="https://zenstack.dev/blog/supabase-alternative#1-separation-from-application-logic">​</a></h3>
<p>As a modern developer, you know the sense of control when you can launch with a one-click. The prerequisite is to keep everything within the codebase. It's not just about convenience; it's about maintaining a single source of truth, ensuring consistency, and streamlining your workflow.</p>
<p>However, for RLS, you have to define authorization directly in the database, not in your source code. Of course, you could store it as an SQL file in your codebase, but you need to rely on SQL migration to ensure consistency. I think it’s the same reason why you seldom see people using stored procedures of databases nowadays despite all the benefits they offer.</p>
<p>What makes the consistency even worse is that you have to duplicate the policy filters in the application code. For example, if you are using the Supabase JS SDK, you have to use the two queries below to get the result:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// First, get the user's spaceIds</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userSpaces </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> supabase</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'SpaceUser'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'spaceId'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">eq</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'userId'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> userId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Extract spaceIds from the result</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> userSpaceIds </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> userSpaces</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">space</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> space</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">spaceId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Now, query the List table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> error </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> supabase</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'List'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'*'</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Why? Because otherwise, you might experience 20x slower query performance, according to the official benchmark of Supabase. 😲</p>
<p><a href="https://supabase.com/docs/guides/database/postgres/row-level-security#add-filters-to-every-query" target="_blank" rel="noopener noreferrer">Add filters to every query | Supabase Docs</a></p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-poor-dx">2. Poor DX<a class="hash-link" aria-label="Direct link to 2. Poor DX" title="Direct link to 2. Poor DX" href="https://zenstack.dev/blog/supabase-alternative#2-poor-dx">​</a></h3>
<p>If you are an SQL expert, you can ignore this one.</p>
<p>Writing SQL itself is not considered a good developer experience (DX) for many developers, with the majority adopting ORM. Moreover, you have to write it in a UI box without the help of Intellisense and often get an obscure error after clicking the save button:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/60896619-cc51-41d8-8b35-57690dd267e4" alt="supabase-error" class="img_XvmG"></p>
<p>The more challenging part is testing and debugging. Honestly, I have no idea about the best practice for it; if you have any tips, please share them in the comments.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3-scalability">3. Scalability<a class="hash-link" aria-label="Direct link to 3. Scalability" title="Direct link to 3. Scalability" href="https://zenstack.dev/blog/supabase-alternative#3-scalability">​</a></h3>
<p>You might feel the aforementioned RLS for <code>List</code> is clear and straightforward, but that’s only for one table. If the access control policy for <code>Todo</code> is the same as <code>List</code>, which is a very common case, what policy do you need to create for <code>Todo</code>?  The answer is that you have to duplicate all the policies of the <code>list</code> for <code>Todo</code>. That’s definitely not DRY(Don’t repeat yourself).</p>
<p>If you don't think it's a big deal, imagine you're lucky enough to grow your Todo SaaS into a team collaboration platform that manages various entities like dashboards, tasks, bugs, projects, etc.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="4-maintainability">4. Maintainability<a class="hash-link" aria-label="Direct link to 4. Maintainability" title="Direct link to 4. Maintainability" href="https://zenstack.dev/blog/supabase-alternative#4-maintainability">​</a></h3>
<p>Let’s say we get a feature request that if a team member can see a Todo list, he will have full access to all the Todos under it, even the ones not owned by him.</p>
<p>Do you know what change you need to make? You need to delete all the policies copied from the <code>List</code> mentioned above and then create a new policy below:</p>
<div class="language-sql codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-sql codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">create</span><span class="token plain"> policy </span><span class="token string" style="color:#e3116c">"Todo"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">on</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"public"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"Todo"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">to</span><span class="token plain"> authenticated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">using</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">EXISTS</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Todo"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"listId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"ownerId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">OR</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">EXISTS</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"List"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token keyword" style="color:#00009f">JOIN</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Space"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">ON</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Space"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token keyword" style="color:#00009f">JOIN</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">ON</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"spaceId"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Space"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Todo"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"listId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"SpaceUser"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token string" style="color:#e3116c">"userId"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">::uuid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">NOT</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">private</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Do you think you can write this kind of policy all by yourselves? I actually asked ChatGPT to write it myself. Even if it's not a problem for you, think about how another team member might feel when he sees it for the first time.</p>
<p>And don't forget to duplicate this change in your application code for performance's sake. 😂</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="alternative-solution">Alternative Solution<a class="hash-link" aria-label="Direct link to Alternative Solution" title="Direct link to Alternative Solution" href="https://zenstack.dev/blog/supabase-alternative#alternative-solution">​</a></h2>
<p>Remember in 2016 when Google I/O Firebase announced its expansion to BaaS? In the same year, another product with an interesting name joined this journey: Graphcool.</p>
<p>Maybe you haven't heard about it because it was sunsetted in 2020, the same year Supabase came out (what a coincidence!).   Why was it sunsetted?  See the detailed explanation on its home page:</p>
<p><a href="https://www.graph.cool/" target="_blank" rel="noopener noreferrer">https://www.graph.cool/</a></p>
<p>TLDR: The team felt that BaaS was too limited for developers to build the next generation of web applications, so they pivoted it to the <a href="https://www.prisma.io/orm" target="_blank" rel="noopener noreferrer">Prisma ORM</a>.</p>
<p>It simplifies database interactions by providing a type-safe query builder, seamless migrations, and an intuitive data modeling language.  While Prisma ORM does provide more flexibility compared to BaaS, it intentionally misses the access control layer as an ORM. Consequently, you have to switch back to implementing Authorization logic at the application level.</p>
<p>Is it possible to bring back the convenience of not writing code for the Authorization like BaaS while maintaining the flexibility of a custom backend?</p>
<p>That's why we built <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> on top of Prisma ORM, adding the missing authorization layer and auto-generating type-safe APIs/hooks. It gives you the same convenience as using BaaS while maintaining flexibility with everything in your codebase.</p>
<p>Let’s cut the crap and see the code directly.  Below are the equivalent ZenStack schema definitions of <code>List</code> and <code>Todo</code> you need to write for the ToDo apps.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">abstract</span><span class="token plain"> model </span><span class="token maybe-class-name">BaseEntity</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id        </span><span class="token known-class-name class-name">String</span><span class="token plain">   </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">uuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    space     </span><span class="token maybe-class-name">Space</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">spaceId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    spaceId   </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    owner     </span><span class="token maybe-class-name">User</span><span class="token plain">     @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ownerId   </span><span class="token known-class-name class-name">String</span><span class="token plain">   @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// can be read by owner or space members </span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">space</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">members</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// when create, owner must be set to current user, and user must be in the space</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> space</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">members</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// when create, owner must be set to current user, and user must be in the space</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// update is not allowed to change owner</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> space</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">members</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">future</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">owner</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> owner</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// can be deleted by owner</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * Model for a Todo list</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">List</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">BaseEntity</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title   </span><span class="token known-class-name class-name">String</span><span class="token plain">  @</span><span class="token function" style="color:#d73a49">length</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token known-class-name class-name">Boolean</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    todos   </span><span class="token maybe-class-name">Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// can't be read by others if it's private</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">deny</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> owner </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * Model for a single Todo</span><br></span><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Todo</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id          </span><span class="token known-class-name class-name">String</span><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">id</span><span class="token plain"> @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">uuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    owner       </span><span class="token maybe-class-name">User</span><span class="token plain">      @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ownerId     </span><span class="token known-class-name class-name">String</span><span class="token plain">    @</span><span class="token keyword" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    list        </span><span class="token maybe-class-name">List</span><span class="token plain">      @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onDelete</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Cascade</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    listId      </span><span class="token known-class-name class-name">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title       </span><span class="token known-class-name class-name">String</span><span class="token plain">    @</span><span class="token function" style="color:#d73a49">length</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    completedAt </span><span class="token maybe-class-name">DateTime</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// same as its parent list</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Keep in mind that with this schema done, although you still need a server to deploy, you hardly need to write any backend code.  ZenStack introspects the schema and installs CRUD APIs to the framework of your choice.  Thanks to the access policy defined in the model, the APIs are fully secure and can be directly exposed to the public.  You can also generate the OpenAPI(swagger) specification.</p>
<p>In fact, you don’t even need to be aware of the API, as ZenStack generates fully typed client hooks that call into the generated APIs.  You can directly use those hooks with intrinsic automatic invalidation and optimistic update support.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/93eaf485-33d1-4512-9c12-58197dedd691" alt="fontend-query" class="img_XvmG"></p>
<p>What about the problems of RLS?  Let’s go through them one by one.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1-single-source-of-truth">1. Single Source of Truth<a class="hash-link" aria-label="Direct link to 1. Single Source of Truth" title="Direct link to 1. Single Source of Truth" href="https://zenstack.dev/blog/supabase-alternative#1-single-source-of-truth">​</a></h3>
<p>The access policies are now defined alongside the database models.  The schema becomes the single source of truth of your backend, enabling an easier understanding of the system as a whole.</p>
<p>Moreover, whether calling from the frontend or backend, you don’t need to use any filter regarding the authorization rules, which will be injected into queries automatically by the ZenStack runtime. The application code you need to write is clean and clear:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// frontend query:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// ZenStack generated hooks only returns the data user is allowed to read</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> lists </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">    </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// server props</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> getServerSideProps</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">GetServerSideProps</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Props</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> params </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getEnhancedPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ZenStack enhanced Prisma client only returns the data user is allowed to read</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> lists </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">findMany</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        props</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> lists </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p>Remember the complex query you need to write for the RLS case mentioned above?</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-good-dx">2. Good DX<a class="hash-link" aria-label="Direct link to 2. Good DX" title="Direct link to 2. Good DX" href="https://zenstack.dev/blog/supabase-alternative#2-good-dx">​</a></h3>
<p>ZenStack comes with a <a href="https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack" target="_blank" rel="noopener noreferrer">VSCode extension</a>. All the basic features of IntelliSense, like autocomplete,  Inline error reporting, Go-to definition, finding the reference, and auto-format,</p>
<p>work as usual.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/316ccc5c-ea79-4614-a13b-3654554e560d" alt="zmodel-autocomplete" class="img_XvmG">
If you have GitHub Copilot, there is a big chance that you don’t have to write the policy yourself:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/user-attachments/assets/b9f100fd-adec-403a-82ef-658aca5b351d" alt="zmodel-copilot" class="img_XvmG"></p>
<p>For testing and debugging, you can enable ZenStack's debug logging by setting a simple flag. Then, you can see all the Prisma queries ZenStack actually calls to the database from the console log:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">prisma</span><span class="token operator" style="color:#393A34">:</span><span class="token plain">info </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">policy</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">findMany</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> list</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token constant" style="color:#36acaa">AND</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">NOT</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">OR</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token constant" style="color:#36acaa">OR</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> owner</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">is</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token constant" style="color:#36acaa">AND</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                space</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  members</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                    some</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">is</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">NOT</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p>Since all the code is actually running on your server, you can set breakpoints in the generated code to debug it step by step.</p>
</blockquote>
<p>ZenStack also provides a REPL CLI for interactive query execution. You can quickly switch between different user contexts and see how the access policies affect the result.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/yJr8zZVj-JA?si=MNeraOCjo__hgFKK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3-scalability-1">3. Scalability<a class="hash-link" aria-label="Direct link to 3. Scalability" title="Direct link to 3. Scalability" href="https://zenstack.dev/blog/supabase-alternative#3-scalability-1">​</a></h3>
<p>Have you noticed that for <code>Todo</code> there is only one line of the policy rule?</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// same as its parent list</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>There is no need to duplicate the policies of <code>List</code> as needed for RLS.</p>
<p>Furthermore, if you need to add a new top-level entity like <code>Bug</code>, all you need is to add the below model in the schema:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Bug</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">BaseEntity</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    title    </span><span class="token known-class-name class-name">String</span><span class="token plain"> @</span><span class="token function" style="color:#d73a49">length</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    priority </span><span class="token maybe-class-name">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>What about the policy rules for it? Thanks to model inheritance, it could inherit all the policies from the abstract base model <code>BaseEntity</code>.</p>
<p>As you can see, ZenStack has provided several features to keep schemas DRY and achieve better scalability.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="4-maintainability-1">4. Maintainability<a class="hash-link" aria-label="Direct link to 4. Maintainability" title="Direct link to 4. Maintainability" href="https://zenstack.dev/blog/supabase-alternative#4-maintainability-1">​</a></h3>
<p>Implementing the requirement change request mentioned above is just adding one parameter</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// full access if the parent list is readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// @@allow('all', check(list))</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   @@</span><span class="token function" style="color:#d73a49">allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Nothing explains better than code here.</p>
<p>Here is the complete runnable project for this SaaS ToDo app:</p>
<p><a href="https://github.com/zenstackhq/sample-todo-nextjs" target="_blank" rel="noopener noreferrer">https://github.com/zenstackhq/sample-todo-nextjs</a></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-art-of-trade-off">The Art of Trade-off<a class="hash-link" aria-label="Direct link to The Art of Trade-off" title="Direct link to The Art of Trade-off" href="https://zenstack.dev/blog/supabase-alternative#the-art-of-trade-off">​</a></h2>
<p>As developers, we know better than others that every sword has two blades.  For all the problems I mentioned with RLS above, the opposite side also shows its advantage.  For example:</p>
<ul>
<li>Although the policy is separate from the application, you don’t have to redeploy the application to let the new policy take effect.</li>
<li>While RLS's SQL policy may seem less intuitive than ZenStack’s, it is more flexible and has a better ecosystem.</li>
</ul>
<p>Software engineering is an art of trade-offs. It involves balancing time and space, stability and flexibility, performance and code complexity, and more. What ZenStack provides is just another alternative; it's up to you to balance these trade-offs and make it an art.</p>
<p>Someone has completed the artwork in production 😉</p>
<blockquote>
<p>We've launched&nbsp;MermaidChart's team feature using ZenStack. Much cleaner and easier to maintain than writing RLS policies or application level checks that will surely leak after some time.<br>
<!-- -->— Sidharth <a href="https://www.mermaidchart.com/" target="_blank" rel="noopener noreferrer">MermaidChart</a></p>
</blockquote>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>supabase</category>
            <category>rls</category>
            <category>auth</category>
            <category>authorization</category>
            <category>baas</category>
            <category>database</category>
            <category>firebase</category>
            <category>zenstack</category>
        </item>
        <item>
            <title><![CDATA[Rendering Prisma Queries With React Table: The Low-Code Way]]></title>
            <link>https://zenstack.dev/blog/react-table</link>
            <guid>https://zenstack.dev/blog/react-table</guid>
            <pubDate>Sun, 21 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This post introduces a low-code approach to rendering database rows loaded with Prisma ORM as a table component using React Table.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-2c5e5a44838e6501a3fc29d4e588d6e2.png" width="760" height="414" class="img_XvmG"></p>
<p><a href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer">React Table</a>, or more precisely, TanStack Table is a headless table UI library. If you're new to this kind of product, you'll probably ask, "What the heck is headless UI"? Isn't UI all about <strong>the head</strong>, after all? It all starts to make sense until you try something like React Table.</p>
<p>For two reasons, tables are one of the nastiest things to build in web UI. First, they are difficult to render well, considering the amount of data they can hold, the interactions they allow, and the need to adapt to different screen sizes. Second, their state is complex: sorting, filtering, pagination, grouping, etc. React Table's philosophy is to solve the second problem well and leave the first entirely to you. It manages the state and logic of a table but doesn't touch the rendering part because:</p>
<blockquote>
<p>Building UI is a very branded and custom experience, even if that means choosing a design system or adhering to a design spec. - tanstack.com</p>
</blockquote>
<p>Tables are most commonly used to render database query results — in modern times, the output of an ORM. In this post, I'll introduce a way of connecting <a href="https://prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a> - the most popular TypeScript ORM, to React Table, with the help of <a href="https://tanstack.com/query" target="_blank" rel="noopener noreferrer">React Query</a> and <a href="https://zenstack.dev/">ZenStack</a>. You'll be amazed by how little code you need to write to render a full-fledged table UI.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-full-stack-setup">A full-stack setup<a class="hash-link" aria-label="Direct link to A full-stack setup" title="Direct link to A full-stack setup" href="https://zenstack.dev/blog/react-table#a-full-stack-setup">​</a></h2>
<p>We need a full-stack application to query a database and render the UI. In this example, I'll use Next.js as the framework, although the approach can be applied to other similar frameworks (like Nuxt, SvelteKit, etc.), or to an application with decoupled front-end and back-end.</p>
<p>We can easily create a new project with <code>npx create-next-app</code>. After that, we need to install several dependencies:</p>
<ul>
<li>Prisma - the ORM</li>
<li>ZenStack - a full-stack toolkit built above Prisma</li>
<li>React Query - the data-fetching library</li>
<li>React Table - the headless table library</li>
</ul>
<p>We'll also use the legendary "North Wind" trading dataset (created by Microsoft many, many years ago) to feed our database. Here's its ERD:</p>
<!-- -->
<p>A <a href="https://github.com/ymc9/react-query-table-zenstack/blob/main/prisma/schema.prisma" target="_blank" rel="noopener noreferrer">Prisma schema file</a> is authored to reflect this database structure.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-free-lunch-api">The "free lunch" API<a class="hash-link" aria-label="Direct link to The &quot;free lunch&quot; API" title="Direct link to The &quot;free lunch&quot; API" href="https://zenstack.dev/blog/react-table#the-free-lunch-api">​</a></h2>
<p>SQL databases are not meant to be consumed from the frontend. You need an API to mediate. You can build such an API in many ways, but here we'll use <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> to "unbuild" it. ZenStack is a full-stack toolkit built above Prisma, and one of the cool things it does is to automagically derive a backend API from the schema.</p>
<p>Setting ZenStack up is straightforward:</p>
<ol>
<li>
<p>Run <code>npx zenstack init</code> to prep the project. It copies the <code>schema.prisma</code> file into <code>schema.zmodel</code> - which is the schema file used by ZenStack. ZModel is a superset of Prisma schema.</p>
</li>
<li>
<p>Whenever you make changes to <code>schema.zmodel</code>, run <code>npx zenstack generate</code> to regenerate the Prisma schema and <code>PrismaClient</code>.</p>
</li>
</ol>
<p>ZenStack can provide a full set of CRUD API with Next.js in a few lines of code:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/api/model/[...path]/route.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> prisma </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/server/db'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NextRequestHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/server/next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NextRequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">getPrisma</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  useAppDir</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DELETE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PATCH</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">POST</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PUT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now you have a set of APIs mounted at <code>/api/model</code> that mirrors <code>PrismaClient</code>:</p>
<ul>
<li>GET <code>/api/model/order/findMany?q=...</code></li>
<li>GET <code>/api/model/order/count?q=...</code></li>
<li>PUT <code>/api/model/order/update</code></li>
<li>...</li>
</ul>
<p>The query parameters and body also follow the corresponding <code>PrismaClient</code> method parameters.</p>
<p>I know a big <strong>🚨 NO THIS IS NOT SECURE 🚨</strong> is flashing in your mind. Hold on, we'll get to that later.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-free-lunch-hooks">The "free lunch" hooks<a class="hash-link" aria-label="Direct link to The &quot;free lunch&quot; hooks" title="Direct link to The &quot;free lunch&quot; hooks" href="https://zenstack.dev/blog/react-table#the-free-lunch-hooks">​</a></h2>
<p>Having a free API is cool, but writing <code>fetch</code> to call it is cumbersome. How about some free query hooks? Yes, add the <code>@zenstackhq/tanstack-query</code> plugin to the ZModel schema, and you'll have a set of fully typed React Query hooks generated for each model:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">plugin</span><span class="token plain"> hooks </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/tanstack-query'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  output </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'src/hooks'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The hooks call into the APIs we installed in the previous section, and they also precisely mirror <code>PrismaClient</code>'s signature:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> useFindManyOrder </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/hooks/order'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// data is typed as `(Order &amp; { details: OrderDetail[]; customer: Customer })[]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> error </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyOrder</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  orderBy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> details</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> customer</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Please note that although React Query and React Table are both from TanStack, you don't have to use them together. React Table is agnostic to the data fetching mechanism. They just happen to play very well together.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="finally-lets-build-the-table">Finally, let's build the table<a class="hash-link" aria-label="Direct link to Finally, let's build the table" title="Direct link to Finally, let's build the table" href="https://zenstack.dev/blog/react-table#finally-lets-build-the-table">​</a></h2>
<p>Creating a basic table is straightforward, You define the columns and then initialize a table instance with data. We'll see how to do it by building a table to display order details.</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// the relation fields included when querying `OrderDetail`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> queryInclude </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    order</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> employee</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    product</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> include</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> category</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> satisfies </span><span class="token maybe-class-name">Prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">OrderDetailFindManyArgs</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// create a column helper to simplify the column definition</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// The `Prisma.OrderDetailGetPayload&lt;typeof queryInclude&gt;` type gives us</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// the shape of the query result</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> columnHelper </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token generic-function function" style="color:#d73a49">createColumnHelper</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">Prisma</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">.</span><span class="token generic-function generic class-name">OrderDetailGetPayload</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name keyword" style="color:#00009f">typeof</span><span class="token generic-function generic class-name"> queryInclude</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> columns </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  columnHelper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">accessor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'order.id'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">header</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Order ID</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  columnHelper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">accessor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'order.orderDate'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">cell</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">info</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> info</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getValue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">toLocaleDateString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">header</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Date</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// other columns ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  columnHelper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">accessor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'order.employee.firstName'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">header</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Employee</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">OrderDetails</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// fetch data with query hooks</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyOrderDetail</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">queryInclude</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// create a table instance</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> table </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useReactTable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> orders </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    columns</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    getCoreRowModel</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getCoreRowModel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>We can then render the table with some basic tsx:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">OrderDetails</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">table</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">thead</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">table</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getHeaderGroups</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">headerGroup</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">tr</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">headerGroup</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">headerGroup</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">header</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">th</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">header</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token function" style="color:#d73a49">flexRender</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  header</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">column</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">columnDef</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">header</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  header</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">th</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">tr</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">thead</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">tbody</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">table</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getRowModel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">rows</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">tr</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">row</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">row</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getVisibleCells</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cell</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">td</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">cell</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token function" style="color:#d73a49">flexRender</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  cell</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">column</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">columnDef</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">cell</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                  cell</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">td</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">tr</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">tbody</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  );</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With the help of column definitions, React Table knows how to fetch data for a cell and transform it as needed. You only need to focus on properly laying the table out.</p>
<p><img decoding="async" loading="lazy" alt="Simple table" src="https://zenstack.dev/assets/images/table-1-e7ba9ab6fabdb905c821384c93c3b419.png" width="2108" height="1168" class="img_XvmG"></p>
<p>What's cool about React Table is that you don't need to flatten the nested query result into tabular form. The columns can be defined to reach into deeply nested objects.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="making-it-fancier">Making it fancier<a class="hash-link" aria-label="Direct link to Making it fancier" title="Direct link to Making it fancier" href="https://zenstack.dev/blog/react-table#making-it-fancier">​</a></h2>
<p>Tables allow you to do many things besides viewing data. Let's use pagination as an example to demonstrate how to enable such interaction in our setup.</p>
<p>React Query has built-in support from front-end pagination. However, since we're rendering database tables, we want the pagination to run at the backend. First, we define a pagination state and set the table to use manual pagination mode (meaning that we handle the pagination ourselves):</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// pagination state</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setPagination</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">PaginationState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  pageIndex</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  pageSize</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PAGE_SIZE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// fetch total row count</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> count </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useCountOrderDetail</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> table </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useReactTable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// pagination</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  manualPagination</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  onPaginationChange</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> setPagination</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  pageCount</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Math</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">ceil</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">count </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PAGE_SIZE</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// state</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  state</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> agination </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Also, update the hooks call to respect the pagination state:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useFindManyOrderDetail</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">queryInclude</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  skip</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pageIndex</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pageSize</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  take</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pageSize</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finally, add navigation buttons:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> table</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">previousPage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript operator" style="color:#393A34">!</span><span class="token tag script language-javascript" style="color:#00009f">table</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">getCanPreviousPage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    Prev</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> table</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">nextPage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript operator" style="color:#393A34">!</span><span class="token tag script language-javascript" style="color:#00009f">table</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">getCanNextPage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    Next</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">span</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">ml-2</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    Page </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">table</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pageIndex</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"> of</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">' '</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">table</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getPageCount</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toLocaleString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This part well demonstrates the value of "headless" UI. You don't need to manage detailed pagination state anymore. Instead, provide the bare minimum logic and let React Table handle the rest. Sorting can be implemented similarly. Check out the link at the end of this post for the complete code.</p>
<p><img decoding="async" loading="lazy" alt="Table with pagination" src="https://zenstack.dev/assets/images/table-2-c90b207745660d3ff9974c17efd16206.png" width="2118" height="1254" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="tons-of-flexibility">Tons of flexibility<a class="hash-link" aria-label="Direct link to Tons of flexibility" title="Direct link to Tons of flexibility" href="https://zenstack.dev/blog/react-table#tons-of-flexibility">​</a></h2>
<p>We've got a pretty cool table end-to-end working now, with roughly 200 lines of code. Less code is only one of the benefits of this combination. It also provides excellent flexibility in every layer of the stack:</p>
<ul>
<li>
<p><strong>Prisma's query</strong></p>
<p>Prisma is known for its concise yet powerful query API. It allows you to do complex joins and aggregations without writing SQL. In our example, our table shows data from five tables, and we barely noticed the complexity.</p>
</li>
<li>
<p><strong>ZenStack's access control</strong></p>
<p>Remember I said we'll get back to the security issue? A real-world API must have an authorization mechanism with it. ZenStack's real power lies in its ability to define access control rules in the data schema. You can define rules like rejecting anonymous users or showing only the orders of the current login employee, etc. Read more details <a href="https://zenstack.dev/docs/2.x/the-complete-guide/part1/access-policy/">here</a>.</p>
</li>
<li>
<p><strong>React Query's fetching</strong></p>
<p>React Query provides great flexibility around how data is fetched, cached, and invalidated. Leverage its power to build a highly responsive UI and reduce the load on the database at same time.</p>
</li>
<li>
<p><strong>React Table's state management</strong></p>
<p>React Table has every aspect of a table's state organized for you. It provides a solid pattern to follow without limiting how you render the table UI.</p>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/react-table#conclusion">​</a></h2>
<p>The evolution of dev tools is like a pendulum swinging backward and forward between simplicity and flexibility. All these years of wisdom have distilled into awesome tools like React Table and React Query, which seem to have found a good balance. They are not the simplest to pick up, but they are simple enough yet wonderfully flexible.</p>
<hr>
<p>The complete sample code: <a href="https://github.com/ymc9/react-query-table-zenstack" target="_blank" rel="noopener noreferrer">https://github.com/ymc9/react-query-table-zenstack</a></p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>prisma</category>
            <category>react-table</category>
            <category>react-query</category>
            <category>zenstack</category>
        </item>
        <item>
            <title><![CDATA[How the "check" Function Helps Keep Your Policies DRY]]></title>
            <link>https://zenstack.dev/blog/check-function</link>
            <guid>https://zenstack.dev/blog/check-function</guid>
            <pubDate>Sat, 13 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This post introduces a typical pattern of access policy duplication in ZenStack schemas, and explains how the new `check` attribute function can help you keep your policies DRY.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-75f7b55f90a5fe846e86d2ff93943608.jpg" width="2048" height="1080" class="img_XvmG"></p>
<p>Among ZenStack's features, the most beloved one is the ability to define access control policies inside the data schema. This ensures that your rules are colocated with the source code, always in sync with the data model, and easy to understand. It arguably provides a superior DX to other solutions like hand-coded authorization logic, or Postgres row-level security.</p>
<p>However, as your application grows more complex, you may find yourself repeating the same policy patterns across multiple models. This post explores one typical pattern of such duplication and demonstrates how the new <code>check()</code> attribute function can help you keep your policies DRY.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-parent-child-duplication-pattern">The parent-child duplication pattern<a class="hash-link" aria-label="Direct link to The parent-child duplication pattern" title="Direct link to The parent-child duplication pattern" href="https://zenstack.dev/blog/check-function#the-parent-child-duplication-pattern">​</a></h2>
<p>Consider a simple Todo application with two models: <code>List</code> and <code>Todo</code>. Each list can have multiple todos. The author of a list has full access to it. A list can also be set as public so anyone can read it. A todo's access control is determined by its containing list: one has the same permissions to a todo as to its parent.</p>
<p>Here's how a ZModel schema for the application might look:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id       </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  public   </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">   User</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  todos</span><span class="token type-class-name">    Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> public</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id     </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name   </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  list</span><span class="token type-class-name">   List</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listId </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">public</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>As you can easily spot, the access policies for <code>Todo</code> are almost identical to those for <code>List</code>. The duplication is not only tedious to write but also error-prone to maintain. If you ever need to change the policy, you have to remember to update it in two places. In a real application, the rules will be more complex, and as a result, the duplication will be more severe and can sometimes get several level deep.</p>
<p>The key to the problem is that from the access control point of view, the child model <code>Todo</code> simply "follows" the parent model <code>List</code>, which is a typical pattern in many applications. We can avoid duplication if we had a way to "delegate" the check of the child model to its parent.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-check-function">The <code>check()</code> function<a class="hash-link" aria-label="Direct link to the-check-function" title="Direct link to the-check-function" href="https://zenstack.dev/blog/check-function#the-check-function">​</a></h2>
<p>The new <code>check()</code> attribute function introduced in ZenStack <a href="https://github.com/zenstackhq/zenstack/releases/tag/v2.3.0" target="_blank" rel="noopener noreferrer">v2.3</a> is designed exactly for such "delegation". The function has the following signature:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">field</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> FieldReference</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> operation String</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Boolean</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ul>
<li>
<p><code>field</code></p>
<p>A relation field to check access for. Must be a non-array field.</p>
</li>
<li>
<p><code>operation</code></p>
<p>An optional argument indicating the CRUD permission kind to check. If the operation is not provided, it defaults to the operation of the policy rule containing it.</p>
</li>
</ul>
<p>You can use the function in a few different forms:</p>
<ol>
<li>
<p>You can explicitly specify the kind of CRUD operation to delegate to:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Child </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Parent</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>Or you can omit the operation so it defaults to the current context:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Child </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Parent</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// here the operation is implicitly 'read'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>You can also delegate "all" operations:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Child </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Parent</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The above is equivalent to:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Child </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Parent</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'update'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>Since <code>check</code> is just a boolean function, you can freely combine it with other conditions:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Child </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Parent</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">status </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'PAID'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="before-and-after">Before and after<a class="hash-link" aria-label="Direct link to Before and after" title="Direct link to Before and after" href="https://zenstack.dev/blog/check-function#before-and-after">​</a></h2>
<p>With this new weapon in hand, we can easily refactor our Todo schema to eliminate the duplication:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> List </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id       </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  public   </span><span class="token entity" style="color:#36acaa">Boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">   User</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  todos</span><span class="token type-class-name">    Todo</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> author</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> public</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Todo </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id     </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name   </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  list</span><span class="token type-class-name">   List</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listId </span><span class="token entity" style="color:#36acaa">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Pretty neat, isn't it?</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="whats-next">What's next?<a class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" href="https://zenstack.dev/blog/check-function#whats-next">​</a></h2>
<p>Having the <code>check</code> function is great for resolving the parent-child duplication pattern. However, there are more related features that can be explored in future versions of ZenStack. Here are some ideas:</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="1--to-many-relations">1. <code>*-to-many</code> relations<a class="hash-link" aria-label="Direct link to 1--to-many-relations" title="Direct link to 1--to-many-relations" href="https://zenstack.dev/blog/check-function#1--to-many-relations">​</a></h3>
<p>You've probably already noticed the limitation: the <code>check</code> function only works for the <code>*-to-one</code> side of a relation. Although I feel it should cover most of the use cases, there might be cases where you want to deal with the "*-to-many" side.</p>
<p>There are two possible ways to add such support:</p>
<ul>
<li>
<p>A: a new set of check functions: <code>checkSome</code>, <code>checkAll</code>, <code>checkNone</code>, etc.</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Parent </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token type-class-name"> Child</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> checkSome</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">children</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>
<p>B: make it work with <a href="https://zenstack.dev/docs/2.x/reference/zmodel-language#collection-predicate-expressions">collection predicate expressions</a></p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Parent </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token type-class-name"> Child</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// check if at least one child is readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">child</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">child</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ul>
<p>What's your preference? Leave a comment below!</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="2-recursive-relations">2. Recursive relations<a class="hash-link" aria-label="Direct link to 2. Recursive relations" title="Direct link to 2. Recursive relations" href="https://zenstack.dev/blog/check-function#2-recursive-relations">​</a></h3>
<p>The <code>zenstack</code> CLI checks for cycles in the <code>check</code> call graph and reports an error if it finds one. This is needed to prevent infinite loops during evaluation. However, there are cases where cyclic delegation is intentionally needed, especially in the case of recursion. For example, if you want to model a Google Drive-like system, you may end up with a recursion like this:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Folder </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  parent</span><span class="token type-class-name"> Folder</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token type-class-name"> Folder</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  permissions</span><span class="token type-class-name"> Permission</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// a folder is readable if it's configured with a permission or if its parent is readable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> permissions</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">[</span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'read'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This will be a hard problem to solve since Prisma inherently doesn't support recursive queries. You can check the following issue for more details:</p>
<div align="center"><a href="https://github.com/prisma/prisma/issues/3725"><img src="https://zenstack.dev/assets/images/recursion-issue-1382db5bc2af4db7eed07e639f9cfeb5.png" alt="Prisma recursion issue" width="600px" style="border-radius:0.5rem"></a></div>
<p>A possible solution is to expand the recursion with a (configurable) finite levels of depth. Is this something your app needs?</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="3-other-forms-of-duplication">3. Other forms of duplication?<a class="hash-link" aria-label="Direct link to 3. Other forms of duplication?" title="Direct link to 3. Other forms of duplication?" href="https://zenstack.dev/blog/check-function#3-other-forms-of-duplication">​</a></h3>
<p>Parent-child is just one of the patterns of duplication out there. Are there other patterns hurting you? Leave a comment or join our <a href="https://discord.gg/Ykhr738dUe" target="_blank" rel="noopener noreferrer">discord server</a> to discuss! We love real-world problems and our innovation never stops.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>zenstack</category>
        </item>
        <item>
            <title><![CDATA[PHP: Laravel, Ruby: Rails, JavaScript:?]]></title>
            <link>https://zenstack.dev/blog/js-fullstack</link>
            <guid>https://zenstack.dev/blog/js-fullstack</guid>
            <pubDate>Tue, 28 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Explain the histrocial reason why JavaScript ecosystem lacks a full-stack framework like Laravel or Rails and the contemporary endeavor for challenge the status quo.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-64ee3034e0ba5cd8e94748a9421acb3c.png" width="1511" height="832" class="img_XvmG">
Recently, there has been a heated discussion on Twitter between JS developers and Laravel and Rails developers. It started with a lengthy tweet from Taylor Otwell, author of <a href="https://laravel.com/" target="_blank" rel="noopener noreferrer">Laravel</a>:</p>
<div align="center"><p><a href="https://x.com/taylorotwell/status/1791468060903096422" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack-docs/assets/16688722/67ecca64-e6e4-4033-bd29-e87dd3e408a1" alt="Taylor Otwell" class="img_XvmG"></a></p></div>
<p>In short, he suggested that the whole JavaScript ecosystem lacks a real "full stack" framework like Laravel or Rails, which could allow a single developer to build the next GitHub, AirBnb, or Shopify.</p>
<p>I deeply empathize with this, as I share the same goal when building ZenStack, the typescript toolkit on top of Prisma ORM. In fact, I have often heard that kind of word from our community:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/a3c3a03a-b5af-49a6-ba79-0c8c845828d2" alt="community-chat" class="img_XvmG"></p>
<p>No one can deny the popularity and fast-paced growth of the JS ecosystem, even among non-members. So why is it?</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="the-historical-reason">The Historical Reason<a class="hash-link" aria-label="Direct link to The Historical Reason" title="Direct link to The Historical Reason" href="https://zenstack.dev/blog/js-fullstack#the-historical-reason">​</a></h2>
<blockquote>
<p>Men make their own history, but they do not make it as they please; they do not make it under self-selected circumstances, but under circumstances existing already, given and transmitted from the past --Karl Marx</p>
</blockquote>
<p>Both PHP and Ruby were designed as server-side languages from the start. PHP was created in 1994 to build dynamic web pages, while Ruby, which appeared in the mid-1990s, was designed for general-purpose programming.</p>
<p>Given their server-side origins, PHP and Ruby were naturally suited for comprehensive frameworks that could handle all aspects of web development, from routing and controllers to database interactions and templating engines. This led to the creation of frameworks like Laravel and Rails to offer a complete, opinionated way to build web apps.</p>
<p>Contrastingly, JavaScript was born as a client-side scripting language for web browsers. It had nothing to do with the backend until 2009, when Node.js was introduced. If you've heard of the "Browser Wars" between Netscape Navigator and Internet Explorer, you're likely aware of the ongoing chaos in the frontend, which continues to drive front-end developers crazy today in the name of browser compatibility. As a result, the early web was about piecing together disparate technologies. Therefore, JavaScript developers became accustomed to modularity, allowing flexibility to mix and match libraries and tools for survival. That's why NPM, which emerged alongside Node.js, has grown at an astonishing rate, quickly becoming the largest software registry in the world.</p>
<p>This different circumstances led to the different developer cultures:</p>
<ul>
<li><strong>PHP/Ruby devs:</strong>&nbsp;"Give me a framework that just works. I want conventions, stability, and a clear path to shipping."</li>
<li><strong>JS devs:</strong>&nbsp;"Don't box me in! I want flexibility, the latest tools, and the freedom to build my way, even if it means more work upfront."</li>
</ul>
<p>As a result, even after expanding to the backend world, a monolithic, "one-size-fits-all" approach could hardly fly in the Javascript ecosystem.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="contemporary-endeavor">Contemporary Endeavor<a class="hash-link" aria-label="Direct link to Contemporary Endeavor" title="Direct link to Contemporary Endeavor" href="https://zenstack.dev/blog/js-fullstack#contemporary-endeavor">​</a></h2>
<p>On one hand, this culture leads to constant evolution, keeping the entire ecosystem exciting and innovative. However, it also results in more decision fatigue and a steeper learning curve for newcomers.</p>
<p>"Where there's muck, there's brass." Some individuals embarked on an adventurous journey to build a Rails-like, battery-included framework to challenge the status quo. Here are some popular examples:</p>
<ul>
<li>
<p><a href="https://redwoodjs.com/" target="_blank" rel="noopener noreferrer">RedwoodJS</a></p>
<p>Full-stack JavaScript framework that integrates React, GraphQL, and Prisma. It simplifies development with a unified setup and automatic code generation, perfect for scalable applications.</p>
</li>
<li>
<p><a href="https://blitzjs.com/" target="_blank" rel="noopener noreferrer">Biltz.js</a></p>
<p>Blitz.js extends Next.js into a full-stack framework featuring a zero-API data layer and Prisma for database access. It aims to simplify development by allowing direct server-side code calls from the frontend.</p>
</li>
<li>
<p><a href="https://adonisjs.com/" target="_blank" rel="noopener noreferrer">AdonisJS</a></p>
<p>AdonisJS is a TypeScript-first web framework for building web apps and API servers. it offers a rich set of features out-of-the-box, including an ORM, authentication, and a powerful CLI, making it ideal for developers seeking a comprehensive and structured development environment.</p>
</li>
</ul>
<p>Will they become the Laravel or Rails of the JS world? It's probably too early to say, but at least RedwoodJS shows great momentum:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/a9876a0b-0096-4a46-915a-a7c4edc13d30" alt="RedwoodJS-trending" class="img_XvmG"></p>
<p>Another bunch of guys are trying to solve this problem by providing “start kits” with <strong>opinionated</strong> battery-included toolkits. Among these, the most popular one is <a href="https://create.t3.gg/" target="_blank" rel="noopener noreferrer">Create-T3-App</a>, which combines Next.js, tRPC, Tailwind CSS, and other powerful tools to give you a solid foundation for building typesafe web applications.</p>
<p>Interestingly, Theo, the creator of T3, seems to be pessimistic about this whole endeavor of JavaScript world:</p>
<div align="center"><p><a href="https://x.com/t3dotgg/status/1792136001345003748" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack-docs/assets/16688722/eb1df393-65d3-490a-8d1e-8395c0b80233" alt="theo" class="img_XvmG"></a></p></div>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="optimistic-future">Optimistic Future<a class="hash-link" aria-label="Direct link to Optimistic Future" title="Direct link to Optimistic Future" href="https://zenstack.dev/blog/js-fullstack#optimistic-future">​</a></h2>
<blockquote>
<p>Any application that can be written in JavaScript, will eventually be written in JavaScript. — Jeff Atwood</p>
</blockquote>
<p>While I'm not entirely convinced by Atwood's law, I do envision a promising future for JavaScript in the field of web development. The reason is simple:</p>
<p><strong>It’s the first time in history that the whole web app could be developed with one programming language.</strong></p>
<p>This is a significant benefit, particularly for novice developers. Thanks to TypeScript's excellent type inference system, we are not only capable of doing it but also willing to do so.</p>
<p>A common critique from Laravel or Rails users is that these frameworks lack a conventional method for modeling the relationship between different entities in your system like the below:</p>
<div align="center"><p><a href="https://x.com/chantastic/status/1791531154212033004" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack-docs/assets/16688722/49167aff-e37e-4603-a042-d1d0f2cf1d28" alt="critique" class="img_XvmG"></a></p></div>
<p>While it may not have reached the level of Laravel or Rails, the current efforts in the JS world have recognized this issue. If you look at the toolkit of the solution mentioned above, you'll find a common name: <a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a></p>
<p>If you haven’t heard about Prisma, it is a modern TypeScript-first ORM that allows you to manage database schemas easily, make queries and mutations with great flexibility, and ensure excellent type safety. This empowers JavaScript developers with a level of data handling sophistication and ease of relationship modeling traditionally found in Laravel and Rails, much like Laravel’s Eloquent ORM.</p>
<p>The <a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> toolkit I’m building on top of Prisma aims to narrow down the gap further. It adds an Authorization layer on top of the schema and then automatically generates both APIs and frontend hooks for you. So, put simply, once you're done with your schema, you're almost done with your backend. You can then choose whatever frontend framework, like React, Vue, or Svelte, to get your UI done.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/43feb3ae-6d97-4ecb-b856-3695040eea7c" alt="ZenStack-schema" class="img_XvmG"></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="begin-with-the-end-in-mind">Begin With the End in Mind<a class="hash-link" aria-label="Direct link to Begin With the End in Mind" title="Direct link to Begin With the End in Mind" href="https://zenstack.dev/blog/js-fullstack#begin-with-the-end-in-mind">​</a></h2>
<p>Will JavaScript ever have its Laravel/Rails moment? Personally, I believe, or at least hope, that having standardized conventions leads to global optimization for the entire ecosystem. However, given the history and culture of JavaScript, achieving this may take a significant amount of time. It's unclear whether AI will accelerate this process or completely overturn it.</p>
<p>So, it seems we just have to wait and see. However, let's not get lost in this debate and forget our original intention, as Lee Robinson says:</p>
<div align="center"><p><a href="https://x.com/leeerob/status/1792215708715122752" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack-docs/assets/16688722/97bb6b0e-ccd6-4f28-a824-b871abf6d802" alt="lee" class="img_XvmG"></a></p></div>
<p>So, I will quote a statement from the <a href="https://www.w3.org/TR/design-principles/#priority-of-constituencies" target="_blank" rel="noopener noreferrer">W3C Web Platform Design Principles</a> at the end:</p>
<blockquote>
<p>User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity.</p>
</blockquote>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>webdev</category>
            <category>javascript</category>
            <category>php</category>
            <category>rails</category>
        </item>
        <item>
            <title><![CDATA[Low-Code Backend Solution for Refine.dev Using Prisma and ZenStack]]></title>
            <link>https://zenstack.dev/blog/refine-dev-backend</link>
            <guid>https://zenstack.dev/blog/refine-dev-backend</guid>
            <pubDate>Mon, 27 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This post introduces how to build a fully-secured backend API for refine.dev projects with minimum code using Prisma and ZenStack.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-56222db32d9ec0c2d9f59c97be8ff5ed.png" width="1792" height="1025" class="img_XvmG"></p>
<p><a href="https://refine.dev/" target="_blank" rel="noopener noreferrer">Refine.dev</a> is a very powerful and popular React-based framework for building web apps with less code. It focuses on providing high-level components and hooks to cover common use cases like authentication, authorization, and CRUD. One of the main reasons for its popularity is that it allows easy integration with many different kinds of backend systems via a flexible adapter design.</p>
<p>This post will focus on the most important type of integration: database CRUD. I'll show how easy it is, with the help of Prisma and ZenStack, to turn your database schema into a fully secured API that powers your refine app. You'll see how we start by defining the data schema and access policies, derive an automatic CRUD API from it, and finally integrate with the Refine app via a "Data Provider."</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-quick-overview-of-the-tools">A quick overview of the tools<a class="hash-link" aria-label="Direct link to A quick overview of the tools" title="Direct link to A quick overview of the tools" href="https://zenstack.dev/blog/refine-dev-backend#a-quick-overview-of-the-tools">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="prisma">Prisma<a class="hash-link" aria-label="Direct link to Prisma" title="Direct link to Prisma" href="https://zenstack.dev/blog/refine-dev-backend#prisma">​</a></h3>
<p><a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a> is a modern TypeScript-first ORM that allows you to manage database schemas easily, make queries and mutations with great flexibility, and ensure excellent type safety.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="zenstack">ZenStack<a class="hash-link" aria-label="Direct link to ZenStack" title="Direct link to ZenStack" href="https://zenstack.dev/blog/refine-dev-backend#zenstack">​</a></h3>
<p><a href="https://zenstack.dev/" target="_blank" rel="noopener noreferrer">ZenStack</a> is a toolkit built above Prisma that adds access control, automatic CRUD web API, etc. It unleashes the ORM's full power for full-stack development.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="authjs">Auth.js<a class="hash-link" aria-label="Direct link to Auth.js" title="Direct link to Auth.js" href="https://zenstack.dev/blog/refine-dev-backend#authjs">​</a></h3>
<p><a href="https://authjs.dev/" target="_blank" rel="noopener noreferrer">Auth.js</a> (successor of NextAuth) is a flexible authentication library that supports many authentication providers and strategies. Although you can use many external services for auth, simply storing everything inside your database is often the easiest way to get started.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="a-blogging-app">A blogging app<a class="hash-link" aria-label="Direct link to A blogging app" title="Direct link to A blogging app" href="https://zenstack.dev/blog/refine-dev-backend#a-blogging-app">​</a></h2>
<p>I'll use a simple blogging app as an example to facilitate the discussion. We'll first focus on implementing the authentication and CRUD with essential access control and then expand to more advanced topics.</p>
<p>You can find the link to the completed project's GitHub repo at the end of the post.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="scaffolding-the-app">Scaffolding the app<a class="hash-link" aria-label="Direct link to Scaffolding the app" title="Direct link to Scaffolding the app" href="https://zenstack.dev/blog/refine-dev-backend#scaffolding-the-app">​</a></h3>
<p>The <code>create-refine-app</code> CLI provides several handy templates to scaffold a new app. We'll use the "Next.js" one so that we can easily contain both the frontend and backend in the same project. Most of the ideas in this post can be applied to a standalone backend project as well.</p>
<p><img decoding="async" loading="lazy" alt="create-refine-app" src="https://zenstack.dev/assets/images/create-refine-app-68faa1a81816f6f19742327167d1634c.png" width="1084" height="770" class="img_XvmG"></p>
<p>We also need to install Prisma and NextAuth:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev prisma</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npm install @prisma/client next-auth@beta</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finally, we'll create the database schema for our app:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">prisma/schema.prisma</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">datasource</span><span class="token plain"> db </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"sqlite"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  url      </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"file:./dev.db"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">generator</span><span class="token plain"> client </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  provider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prisma-client-js"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id            </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@id</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name          </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  email         </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">   </span><span class="token function" style="color:#d73a49">@unique</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  emailVerified </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  image         </span><span class="token entity" style="color:#36acaa">String</span><span class="token operator" style="color:#393A34">?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt     </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  updatedAt     </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain">  </span><span class="token function" style="color:#d73a49">@updatedAt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  accounts</span><span class="token type-class-name">      Account</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  sessions</span><span class="token type-class-name">      Session</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  password      </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posts</span><span class="token type-class-name">         Post</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id        </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">   </span><span class="token function" style="color:#d73a49">@id</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cuid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  updatedAt </span><span class="token entity" style="color:#36acaa">DateTime</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">@updatedAt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  title     </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  content   </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  status    </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">   </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"draft"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  author</span><span class="token type-class-name">    User</span><span class="token plain">     </span><span class="token function" style="color:#d73a49">@relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token type-args">fields:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">authorId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token type-args">references:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  authorId  </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Account </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Session </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> VerificationToken </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-info admonition_al3P alert alert--info"><div class="admonitionHeading_Kjwq"><span class="admonitionIcon_gHdU"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_u1Ss"><p>The <code>Account</code>, <code>Session</code>, and <code>VerificationToken</code> models are <a href="https://authjs.dev/getting-started/adapters/prisma#schema" target="_blank" rel="noopener noreferrer">required by Auth.js</a>.</p></div></div>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="building-authentication">Building authentication<a class="hash-link" aria-label="Direct link to Building authentication" title="Direct link to Building authentication" href="https://zenstack.dev/blog/refine-dev-backend#building-authentication">​</a></h3>
<p>The focus of this post will be data access and access control. However, they are only possible with an authentication system in place. We'll use simple credential-based authentication in this app. The implementation involves creating an Auth.js configuration, installing an API route to handle auth requests, and implementing a Refine "Authentication Provider".</p>
<p>I won't elaborate on the details of this part, but you can find the completed code <a href="https://github.com/ymc9/refine-nextjs-zenstack/tree/main/src/providers/auth-provider" target="_blank" rel="noopener noreferrer">here</a>. It should get the registration, login, and session management parts working.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="set-up-access-control">Set up access control<a class="hash-link" aria-label="Direct link to Set up access control" title="Direct link to Set up access control" href="https://zenstack.dev/blog/refine-dev-backend#set-up-access-control">​</a></h3>
<p>There are many ways to implement access control. People typically put the check in the API layer with imperative code. ZenStack offers a unique and powerful way to do it declaratively inside the database schema. Let's see how it works.</p>
<p>First, let's initialize the project for ZenStack:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack@latest init</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>It'll install a few dependencies and copies over the <code>prisma/schema.prisma</code> file to <code>/schema.zmodel</code>. ZModel is a superset of Prisma Schema Language that adds more features like access control.</p>
<p>Next, we'll add policy rules to the schema:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// everybody can signup</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// full access by self</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// allow read for all signin users</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> status </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'published'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// full access by author</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'all'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> author </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>As you can see, the overall schema still looks very similar to the original Prisma schema. The <code>@@allow</code> directive defines access control rules. The <code>auth()</code> function returns the current authenticated user. We'll see how it's connected with the authentication system next.</p>
<p>The most straightforward way to use ZenStack is to create an "enhancement" wrapper around the Prisma client. First, run the CLI to generate JS modules that support the enforcement of policies:</p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">npx zenstack generate</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, you can call the <code>enhance</code> API to create an enhanced PrismaClient.</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> session </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> session</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">user</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Besides the <code>prisma</code> instance, the <code>enhance</code> function also takes a second argument that contains the current user. The user object provides value to the <code>auth()</code> function call in the schema at runtime.</p>
<p>The enhanced PrismaClient has the same API as the original one, but it will enforce the policy rules automatically for you.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="automatic-crud-api">Automatic CRUD API<a class="hash-link" aria-label="Direct link to Automatic CRUD API" title="Direct link to Automatic CRUD API" href="https://zenstack.dev/blog/refine-dev-backend#automatic-crud-api">​</a></h3>
<p>Having the ORM instance enhanced with access control capabilities is great. We can now implement CRUD APIs without writing imperative authorization code as long as we use the enhanced client. However, wouldn't it be even cooler if the CRUD APIs were automatically derived from the schema?</p>
<p>ZenStack makes it possible by providing a set of server adapters for popular Node.js frameworks. Using it with Next.js is easy. You'll only need to create an API route handler:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/api/model/[...path]/route.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> auth </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/auth'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> prisma </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/db'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> enhance </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/runtime'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NextRequestHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@zenstackhq/server/next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// create an enhanced Prisma client with user context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getPrisma</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> session </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> session</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">user</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NextRequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getPrisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useAppDir</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DELETE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PATCH</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">POST</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handler </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PUT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You then have a set of CRUD APIs served at "/api/model/[Model Name]/...". The APIs closely resemble PrismaClient's API:</p>
<ul>
<li><code>/api/model/post/findMany</code></li>
<li><code>/api/model/post/create</code></li>
<li>...</li>
</ul>
<p>You can find the detailed API specification <a href="https://zenstack.dev/docs/2.x/reference/server-adapters/api-handlers/rpc" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="implementing-a-data-provider">Implementing a data provider<a class="hash-link" aria-label="Direct link to Implementing a data provider" title="Direct link to Implementing a data provider" href="https://zenstack.dev/blog/refine-dev-backend#implementing-a-data-provider">​</a></h3>
<p>We've got the backend APIs ready. Now, the only missing piece is a Refine "Data Provider", which talks to the API to fetch and update data. The following code snippet shows how the <code>getList</code> method is implemented. Refine's data provider's data structure is conceptually very close to Prisma, and we only need to do some lightweighted translation:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/providers/data-provider/index.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dataProvider</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> DataProvider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">getList</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">TData </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">BaseRecord</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> BaseRecord</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      params</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> GetListParams</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">GetListResponse</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">TData</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> queryArgs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// filtering</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">filters </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">filters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> filters </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">filters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">filter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token function" style="color:#d73a49">transformFilter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">filter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">filters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          queryArgs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">where </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">AND</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> filters </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          queryArgs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">where </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> filters</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// sorting</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sorters </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sorters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryArgs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">orderBy </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sorters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sorter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sorter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">field</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> sorter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">order</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// pagination</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">mode </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'server'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">current </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pageSize </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryArgs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">take </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pageSize</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryArgs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">skip </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">current </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pagination</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pageSize</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// call the API to fetch data and count</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> count</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">all</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">fetchData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/findMany'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> queryArgs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">fetchData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/count'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> queryArgs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> total</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> count </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With the data provider in place, we now have a fully working CRUD UI.</p>
<p><img decoding="async" loading="lazy" alt="CRUD UI" src="https://zenstack.dev/assets/images/crud-ui-b18182273cabe5754b3db867577cceb5.png" width="1934" height="1068" class="img_XvmG"></p>
<p>You can sign up for two accounts and verify that the access control rules are working as expected - draft posts are only visible to the author.</p>
<h3 class="anchor anchorWithStickyNavbar_cNxG" id="bonus-guarding-ui-with-permission-checker">Bonus: guarding UI with permission checker<a class="hash-link" aria-label="Direct link to Bonus: guarding UI with permission checker" title="Direct link to Bonus: guarding UI with permission checker" href="https://zenstack.dev/blog/refine-dev-backend#bonus-guarding-ui-with-permission-checker">​</a></h3>
<p>Let's add one more challenge to the problem: the users of our app will have two roles:</p>
<ul>
<li>Reader: can only read published posts</li>
<li>Writer: can create new posts</li>
</ul>
<p>Our schema needs to be updated accordingly:</p>
<div class="language-zmodel codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">schema.zmodel</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-zmodel codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> User </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  role          </span><span class="token entity" style="color:#36acaa">String</span><span class="token plain">    </span><span class="token function" style="color:#d73a49">@default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Reader'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">model</span><span class="token plain"> Post </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// allow read for all signin users</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> status </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'published'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// allow "Writer" users to create</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">role </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Writer'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// full access by author</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">@@allow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'read,update,delete'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> author </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now, if you try to create a new post with a "Reader" account, you'll see the following error:</p>
<p><img decoding="async" loading="lazy" alt="Access denied" src="https://zenstack.dev/assets/images/access-denied-b6c8ae7e90574c2c0903f9d73d53837b.png" width="1710" height="1014" class="img_XvmG"></p>
<p>The operation is denied correctly according to the rules. However, it's not an entirely user-friendly experience. It'd be nice to prevent the "Create" button from appearing in the first place. This can be achieved by combining two additional features from Refine and ZenStack:</p>
<ul>
<li>Refine allows you to implement an "Access Control Provider" to verdict whether the current user has permission to perform an action.</li>
<li>ZenStack's enhanced PrismaClient has an extra <code>check</code> API for inferring permission based on the policy rules. The <code>check</code> API is also available in the automatic CRUD API.</li>
</ul>
<div class="theme-admonition theme-admonition-info admonition_al3P alert alert--info"><div class="admonitionHeading_Kjwq"><span class="admonitionIcon_gHdU"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_u1Ss"><p>ZenStack's <code>check</code> API doesn't query the database. It's based on logical inference from the policy rules. See more details <a href="https://zenstack.dev/docs/2.x/guides/check-permission" target="_blank" rel="noopener noreferrer">here</a>.</p></div></div>
<p>Let's see how these two pieces are put together. First, implement an <code>AccessControlProvider</code>:</p>
<div class="language-ts codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/providers/access-control-provider/index.ts</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-ts codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> accessControlProvider</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AccessControlProvider </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  can</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> resource</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> action </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> CanParams</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">CanReturnType</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">action </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// make a request to "/api/model/:resource/check?q={operation:'create'}"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">/api/model/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">resource</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">/check</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      url </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'?q='</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">encodeURIComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token constant" style="color:#36acaa">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                operation</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'create'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> resp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">resp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ok</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> can</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> resp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> can</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> can</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    buttons</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        enableAccessControl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        hideIfUnauthorized</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryOptions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then, register the provider to the top-level <code>Refine</code> component:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/layout.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Refine</span><span class="token tag" style="color:#00009f"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">accessControlProvider</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> accessControlProvider </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">...</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>You'll immediately notice the difference that, with a "Reader" user, the "Create" button is grayed out and disabled.</p>
<p><img decoding="async" loading="lazy" alt="Create button disabled" src="https://zenstack.dev/assets/images/create-button-disabled-c0496c233aeb3d34b26cb842b380759c.png" width="1712" height="584" class="img_XvmG"></p>
<p>However, you can still directly navigate to the "/blog-post/create" URL to access the create form. We can prevent that by using Refine's <code>CanAccess</code> component to guard it:</p>
<div class="language-tsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_XHCH">src/app/blog-post/create/page.tsx</div><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-tsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">CanAccess</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag attr-name" style="color:#00a4db">resource</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">post</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag attr-name" style="color:#00a4db">action</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">create</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag attr-name" style="color:#00a4db">fallback</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript tag punctuation" style="color:#393A34">&lt;</span><span class="token tag script language-javascript tag" style="color:#00009f">div</span><span class="token tag script language-javascript tag punctuation" style="color:#393A34">&gt;</span><span class="token tag script language-javascript plain-text" style="color:#00009f">Not Allowed</span><span class="token tag script language-javascript tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag script language-javascript tag" style="color:#00009f">div</span><span class="token tag script language-javascript tag punctuation" style="color:#393A34">&gt;</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Create</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">...</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">CanAccess</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Mission accomplished! We've also done it elegantly without hard coding any permission logic in the UI. Everything about access control is still centralized in the ZModel schema.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="conclusion">Conclusion<a class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" href="https://zenstack.dev/blog/refine-dev-backend#conclusion">​</a></h2>
<p>Refine.dev is a great tool for building complex UI without writing complex code. Combined with the superpowers of Prisma and ZenStack, we've now got a full-stack, low-code solution with excellent flexibility.</p>
<hr>
<p>The completed sample project is here: <a href="https://github.com/ymc9/refine-nextjs-zenstack" target="_blank" rel="noopener noreferrer">https://github.com/ymc9/refine-nextjs-zenstack</a>.</p>]]></content:encoded>
            <author>yiming@whimslab.io (Yiming)</author>
            <category>refine</category>
            <category>react</category>
            <category>authorization</category>
        </item>
        <item>
            <title><![CDATA[Stories Behind ZenStack V2]]></title>
            <link>https://zenstack.dev/blog/v2-stories</link>
            <guid>https://zenstack.dev/blog/v2-stories</guid>
            <pubDate>Fri, 26 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[the main features of ZenStack V2 and the stories behind it.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Cover Image" src="https://zenstack.dev/assets/images/cover-300f682a1591c5bc53ef7be0e649295b.jpg" width="1920" height="1280" class="img_XvmG"></p>
<p>After polishing ZenStack V2 in the future branch for more than two months, we are happy to make it official now. I would like to take this opportunity to briefly show you the main features of V2 and the stories behind it.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="polymorphic-relations">Polymorphic Relations<a class="hash-link" aria-label="Direct link to Polymorphic Relations" title="Direct link to Polymorphic Relations" href="https://zenstack.dev/blog/v2-stories#polymorphic-relations">​</a></h2>
<p>This feature is actually the reason we created V2. It's one of the most desired features of Prisma, which you can see from the reactions to the two related GitHub issues:</p>
<ul>
<li><a href="https://github.com/prisma/prisma/issues/2505" target="_blank" rel="noopener noreferrer">Support for a Union type #2505</a></li>
<li><a href="https://github.com/prisma/prisma/issues/1644" target="_blank" rel="noopener noreferrer">Support for Polymorphic Associations #1644</a></li>
</ul>
<p>Some folks even think that an ORM is incomplete without polymorphism support.</p>
<p>As the believer and enhancer of Prisma, one of our strategies is to pick up where Prisma has left.  So, it did come to our radar at the early stage:</p>
<p><a href="https://github.com/zenstackhq/zenstack/issues/430" target="_blank" rel="noopener noreferrer">Support for Polymorphic Associations #430</a></p>
<p>It quickly became one of the most desired features of ZenStack too.  Not only for the reactions themselves but also because more issues actually depend on it:</p>
<ul>
<li><a href="https://github.com/zenstackhq/zenstack/issues/653" target="_blank" rel="noopener noreferrer">ZenStack does not let me define multiple fields that are referencing the same model #653</a></li>
<li><a href="https://github.com/zenstackhq/zenstack/issues/613" target="_blank" rel="noopener noreferrer">Support implicit inversed relationship #613</a></li>
</ul>
<p>However, we know it’s a non-trivial task.  Therefore, instead of rushing into the implementation, we wrote a blog post first to explain our approach and send it to the communities of both Prisma and ZenStack to get their feedback:</p>
<p><a href="https://zenstack.dev/blog/polymorphism" target="_blank" rel="noopener noreferrer">Tackling Polymorphism in Prisma</a></p>
<p>The feedback has exceeded our expectations. One guy even refers to it as <strong>Christmas magic</strong>:</p>
<p><a href="https://github.com/prisma/prisma/issues/1644#issuecomment-1867913252" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/efb6fef1-c5be-4a72-8a84-837e405f2da0" alt="github-comments" class="img_XvmG"></a></p>
<p>Gaining enough confidence, we began working on the implementation. Our confidence was further boosted when Prisma mentioned it as a third-party solution in its official blog, even though the feature was still in the alpha stage:</p>
<p><a href="https://www.prisma.io/docs/orm/prisma-schema/data-model/table-inheritance#third-party-solutions" target="_blank" rel="noopener noreferrer">Table inheritance | Prisma Documentation</a></p>
<p>Now, you can fully enjoy it with the latest version of ZenStack:</p>
<p><a href="https://zenstack.dev/docs/2.x/guides/polymorphism" target="_blank" rel="noopener noreferrer">Polymorphic Relations</a></p>
<p>Why should you use it? Check out the below post to illustrate the benefit with a real example:</p>
<p><a href="https://zenstack.dev/blog/ocp" target="_blank" rel="noopener noreferrer">End-To-End Polymorphism: From Database to UI, Achieving SOLID Design</a></p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="edge-support">Edge Support<a class="hash-link" aria-label="Direct link to Edge Support" title="Direct link to Edge Support" href="https://zenstack.dev/blog/v2-stories#edge-support">​</a></h2>
<p>This is a surprising gift we received from Prisma.  Previously, when a user asked if ZenStack could run on Edge, we would direct them to the <a href="https://www.prisma.io/data-platform/accelerate" target="_blank" rel="noopener noreferrer">Prisma Accelerate</a> proxy, as it was the only way to do it.  However, not all users are willing to use another service. This situation is frustrating for us because, unlike resolving polymorphism, we can't fully address it from ZenStack unless reimplementing the entire Prisma engine.</p>
<p>Fortunately, Prisma sent us <a href="https://www.prisma.io/blog/prisma-orm-support-for-edge-functions-is-now-in-preview" target="_blank" rel="noopener noreferrer">a fantastic gift</a> at the start of 2024. Without hesitation, we began tweaking ZenStack to ensure its compatibility with the edge runtime.  Even though the workload is minimal compared to polymorphism, we gained substantial knowledge throughout the process. If you're also planning to adapt to the Edge runtime, we hope our experience can save you some time:</p>
<p><a href="https://zenstack.dev/blog/adapt-to-edge" target="_blank" rel="noopener noreferrer">Adapting ZenStack to the Edge: Our Struggles and Learnings</a></p>
<p>By the way, Edge support is in preview for both Prisma and ZenStack, so if you run into any issues, feel free to contact us via <a href="https://twitter.com/zenstackhq" target="_blank" rel="noopener noreferrer">Twitter</a>, <a href="https://discord.gg/Ykhr738dUe" target="_blank" rel="noopener noreferrer">Discord</a>, or <a href="https://github.com/zenstackhq/zenstack" target="_blank" rel="noopener noreferrer">GitHub</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="auth-in-default"><code>Auth()</code> in <code>Default()</code><a class="hash-link" aria-label="Direct link to auth-in-default" title="Direct link to auth-in-default" href="https://zenstack.dev/blog/v2-stories#auth-in-default">​</a></h2>
<p>This is a very special feature.  The brilliant idea came from one of our users at a very early time:</p>
<p><a href="https://github.com/zenstackhq/zenstack/issues/310" target="_blank" rel="noopener noreferrer">Support for auth() in @default annotation #310</a></p>
<p>The best part is that right before we started to consider whether to implement it in V2,  another user sent out a PR for it:</p>
<p><a href="https://github.com/zenstackhq/zenstack/pull/958" target="_blank" rel="noopener noreferrer">Support for auth() in @default attribute #958</a></p>
<p>This is the first feature of ZenStack that is fully implemented by the community. We would like to express our gratitude by sharing the author’s <a href="https://github.com/Azzerty23" target="_blank" rel="noopener noreferrer">GitHub profile link</a> here.</p>
<p>It once again shows the benefit of using declarative schema to reduce the duplication of imperative code:</p>
<ul>
<li>before<!-- -->
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//schema.zmodel</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Post</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  owner </span><span class="token maybe-class-name">User</span><span class="token plain"> @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId </span><span class="token maybe-class-name">Int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//xxx.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token literal-property property" style="color:#36acaa">owner</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">connect</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token literal-property property" style="color:#36acaa">title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post1'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
<li>after<!-- -->
<div class="language-jsx codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-jsx codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//schema.zmodel</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token maybe-class-name">Post</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  owner </span><span class="token maybe-class-name">User</span><span class="token plain"> @</span><span class="token function" style="color:#d73a49">relation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fields</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ownerId</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">references</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ownerId </span><span class="token maybe-class-name">Int</span><span class="token plain"> @</span><span class="token keyword module" style="color:#00009f">default</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">auth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// &lt;- assign ownerId automatically</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//xxx.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> db </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enhance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Post1'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_cNxG" id="vscode-auto-formatting">VSCode Auto-Formatting<a class="hash-link" aria-label="Direct link to VSCode Auto-Formatting" title="Direct link to VSCode Auto-Formatting" href="https://zenstack.dev/blog/v2-stories#vscode-auto-formatting">​</a></h2>
<p>This is the feature I regret the most. In fact, I would call it a fix instead of a feature because I did implement it in V1:</p>
<p><a href="https://twitter.com/jiashenggo/status/1720036341642154260" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/e7aff6aa-568a-4edf-aec9-44e741f2ca22" alt="twitter-link" class="img_XvmG"></a></p>
<p>I mentioned “minimize disruptions for prisma users”, but you know what? It was actually a lie. Although I tried to make ZModel look as same as Prisma, it used a different indentation style:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/560d8544-8197-4996-890f-ca42b2613e5c" alt="style-compare" class="img_XvmG"></p>
<p>Which one do you think looks better? 😉&nbsp;I convinced myself that the left one was better due to its consistency with TS style. Why need consistency with TS? Because it includes the access policy code.</p>
<p>It turns out that all of that was just an excuse to avoid making changes.  Even when I realized it soon, I found another excuse that the change would disturb existing users.  You can see how skilled our mind is at finding excuses to avoid work.  However, it struggles to estimate the actual workload accurately because it attempts to avoid it.</p>
<p>The total time I spent to implement it is just a couple of hours which even includes flags both in VSCode extension and CLI to give the user the option to preserve the old style if he likes:</p>
<p><img decoding="async" loading="lazy" src="https://github.com/zenstackhq/zenstack/assets/16688722/c83a2aeb-cb7f-4f1a-b966-6943db558527" alt="vscode-option" class="img_XvmG"></p>
<div class="language-bash codeBlockContainer_S1EK theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_XoNK"><pre tabindex="0" class="prism-code language-bash codeBlock_nRvc thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_OiIy"><span class="token-line" style="color:#393A34"><span class="token plain">Usage: zenstack format [options]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Format a ZenStack schema file.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Options:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --no-prisma-style  do not use prisma style</span><br></span></code></pre><div class="buttonGroup_tGwx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_kI3u" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_vCVj"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_yzqu"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Don't let inertia obscure the truth; use your heart to find the right path and pursue it.</p>
<h1>Final Words</h1>
<p>You can find the complete change detail in the below post:</p>
<p><a href="https://zenstack.dev/docs/2.x/upgrade-v2" target="_blank" rel="noopener noreferrer">Upgrading to V2</a></p>
<p>In the Game of Thrones series finale, Tyrion Lannister said the following words:</p>
<blockquote>
<p>What unites people? Armies? Gold? Flags? Stories. There's nothing in the world more powerful than a good story.</p>
</blockquote>
<p>These features will probably be forgotten as time passes or even not used at all by most people. But I hope the stories behind them could still have some meaning for you.</p>]]></content:encoded>
            <author>jiasheng@whimslab.io (Jiasheng)</author>
            <category>zenstack</category>
            <category>polymorphism</category>
            <category>edge</category>
            <category>prisma</category>
            <category>vscode</category>
        </item>
    </channel>
</rss>