ZenStack

Fullstack TypeScript toolkit that enhances Prisma ORM with flexible Authori...

README

zenstack


What it is


ZenStack is a Node.js/TypeScript toolkit that simplifies the development of a web app's backend. It enhances Prisma ORM with a flexible Authorization layer and auto-generated, type-safe APIs/hooks, unlocking its full potential for full-stack development.

Our goal is to let you save time writing boilerplate code and focus on building real features!

How it works


Read full documentation at 👉🏻 zenstack.dev. Join Discord for feedback and questions.


ZenStack incrementally extends Prisma's power with the following four layers:

1. ZModel - an extended Prisma schema language


ZenStack introduces a data modeling language called "ZModel" - a superset of Prisma schema language. It extended Prisma schema with custom attributes and functions and, based on that, implemented a flexible access control layer around Prisma.

  1. ```ts
  2. // base.zmodel
  3. abstract model Base {
  4.     id String @id
  5.     author User @relation(fields: [authorId], references: [id])
  6.     authorId String

  7.     // 🔐 allow full CRUD by author
  8.     @@allow('all', author == auth())
  9. }
  10. ```

  1. ```ts
  2. // schema.zmodel
  3. import "base"
  4. model Post extends Base {
  5.     title String
  6.     published Boolean @default(false)

  7.     // 🔐 allow logged-in users to read published posts
  8.     @@allow('read', auth() != null && published)
  9. }
  10. ```

The zenstack CLI transpiles the ZModel into a standard Prisma schema, which you can use with the regular Prisma workflows.

2. Runtime enhancements to Prisma client


At runtime, transparent proxies are created around Prisma clients for intercepting queries and mutations to enforce access policies.

  1. ```ts
  2. import { enhance } from '@zenstackhq/runtime';

  3. // a regular Prisma client
  4. const prisma = new PrismaClient();

  5. async function getPosts(userId: string) {
  6.     // create an enhanced Prisma client that has access control enabled
  7.     const enhanced = enhance(prisma, { user: userId });

  8.     // only posts that're visible to the user will be returned
  9.     return enhanced.post.findMany();
  10. }
  11. ```

3. Automatic RESTful APIs through server adapters


Server adapter packages help you wrap an access-control-enabled Prisma client into backend CRUD APIs that can be safely called from the frontend. Here's an example for Next.js:

  1. ```ts
  2. // pages/api/model/[...path].ts

  3. import { requestHandler } from '@zenstackhq/next';
  4. import { enhance } from '@zenstackhq/runtime';
  5. import { getSessionUser } from '@lib/auth';
  6. import { prisma } from '@lib/db';

  7. // Mount Prisma-style APIs: "/api/model/post/findMany", "/api/model/post/create", etc.
  8. // Can be configured to provide standard RESTful APIs (using JSON:API) instead.
  9. export default requestHandler({
  10.     getPrisma: (req, res) => enhance(prisma, { user: getSessionUser(req, res) }),
  11. });
  12. ```

4. Generated client libraries (hooks) for data access


Plugins can generate strong-typed client libraries that talk to the aforementioned APIs. Here's an example for React:

  1. ```tsx
  2. // components/MyPosts.tsx

  3. import { useFindManyPost } from '@lib/hooks';

  4. const MyPosts = () => {
  5.     // list all posts that're visible to the current user, together with their authors
  6.     const { data: posts } = useFindManyPost({
  7.         include: { author: true },
  8.         orderBy: { createdAt: 'desc' },
  9.     });

  10.     return (
  11.         <ul>
  12.             {posts?.map((post) => (
  13.                 <li key={post.id}>
  14.                     {post.title} by {post.author.name}
  15.                 </li>
  16.             ))}
  17.         </ul>
  18.     );
  19. };
  20. ```