Prisma Catalyst For Full-stack Development
A toolkit that supercharges Prisma ORM with a powerful access control layer, unlocking its full potential for web development.

A Power Pack for Prisma

Easy Access Control
Imperative authorization code is brittle and hard to maintain. ZenStack allows you to define access policies declaratively right inside your data model.

Auto-generated API
Data access APIs are automatically generated. Thanks to the built-in access control support, these APIs are safe to be called directly from the frontend.

E2E Type Safety
No more duplicating type definitions and syncing changes. Use one single toolkit to generate types for your entire stack, and enjoy flawless auto-completion.
From Database to UI in 4 Steps
Define data model
ZenStack provides a data modeling language that's a superset of Prisma schema. It adds custom attributes, functions, and standard attributes for declaring access policies and validation rules.
In this example, you can see the usage of @allow
and @email
attributes for attaching access policies and validation rules. These will be enforced automatically in the backend services by using an enhanced Prisma client.
Most people with Prisma experiences find it easy to pick up ZenStack. Prisma schema itself is also very intuitive, so don't worry if it's new to you.
prisma
model User {id String @id// @email is a field validation ruleemail String @email// @password marks field to be hashed (using bcrypt) on save// @omit indicates the field should be filtered when the entity is returnedpassword String @password @omitposts Post[]// policy: everybody can signup@@allow('create', true)// policy: allow full CRUD by self@@allow('all', auth() == this)}model Post {id String @idtitle Stringpublished Boolean @default(false)author User @relation(fields: [authorId], references: [id])authorId String// policy: allow logged-in users to read published posts@@allow('read', auth() != null && published)// policy: allow full CRUD by author// auth() is a built-in function that returns current user@@allow('all', author == auth())}
prisma
model User {id String @id// @email is a field validation ruleemail String @email// @password marks field to be hashed (using bcrypt) on save// @omit indicates the field should be filtered when the entity is returnedpassword String @password @omitposts Post[]// policy: everybody can signup@@allow('create', true)// policy: allow full CRUD by self@@allow('all', auth() == this)}model Post {id String @idtitle Stringpublished Boolean @default(false)author User @relation(fields: [authorId], references: [id])authorId String// policy: allow logged-in users to read published posts@@allow('read', auth() != null && published)// policy: allow full CRUD by author// auth() is a built-in function that returns current user@@allow('all', author == auth())}
Generate API and client code
Run the zenstack
CLI to generate code from the schema, typically including Prisma schema, access policy rules, strongly typed frontend data query libraries (hooks), and tRPC routers (if you've enabled the plugin).
The toolkit is extensible, so you can write your own plugins to run custom code generation. Plugins have access to the AST parsed from the schema and can declare custom attributes and functions.
Mount CRUD services as an API route
To add a set of APIs that wrap around the database, use a framework-specific helper to create a request handler and install it as an API route. The example here demonstrates the integration with Next.js. Check out the documentation for guides about other frameworks.
The services are a thin wrapper around Prisma and expose all essential query and mutation operations, like findMany
, create
, update
, aggregate
, etc.
If you're a fan of tRPC, you can also use the tRPC plugin to generate routers and include them in your tRPC setup.
ts
// Next.js integration example// pages/api/model/[...path].ts// the standard Prisma clientconst prisma = new PrismaClient();// create a Next.js API endpoint handlerexport default requestHandler({// set up a callback to get a database instance for handling the requestgetPrisma: async (req, res) => {// get current user in the sessionconst user = await getSessionUser(req, res);// return an enhanced Prisma client that enforces access policiesreturn withPresets(prisma, { user });},});
ts
// Next.js integration example// pages/api/model/[...path].ts// the standard Prisma clientconst prisma = new PrismaClient();// create a Next.js API endpoint handlerexport default requestHandler({// set up a callback to get a database instance for handling the requestgetPrisma: async (req, res) => {// get current user in the sessionconst user = await getSessionUser(req, res);// return an enhanced Prisma client that enforces access policiesreturn withPresets(prisma, { user });},});
Query and mutate from the frontend
You can now use the generated frontend library (hooks) to talk to the API. The library is fully typed and offers exactly the same programming experiences as using a Prisma client on the server side. This example shows how to query in a React component.
All client requests are governed by the access policies defined in the schema, so even if you don't add any filter, the client can't accidentally read or write data that's not supposed to be allowed.
tsx
// Next.js example for a blog post list component// components/posts.tsxconstPosts :FC = () => {// "usePost" is a generated hooks methodconst {findMany } =usePost ();// list unpublished posts together with their author's data,// the result "posts" only include entities that are readable// to the current userconstposts =findMany ({where : {published : false },include : {author : true },orderBy : {updatedAt : 'desc' },});// entities are accurately typed based on the query structurereturn (<ul >{posts .map ((post ) => (<li key ={post .id }>{post .title } by {post .author .e }</li >))}</ul >);};
tsx
// Next.js example for a blog post list component// components/posts.tsxconstPosts :FC = () => {// "usePost" is a generated hooks methodconst {findMany } =usePost ();// list unpublished posts together with their author's data,// the result "posts" only include entities that are readable// to the current userconstposts =findMany ({where : {published : false },include : {author : true },orderBy : {updatedAt : 'desc' },});// entities are accurately typed based on the query structurereturn (<ul >{posts .map ((post ) => (<li key ={post .id }>{post .title } by {post .author .e }</li >))}</ul >);};
What's in the Whole Package
Intuitive API
Prisma provides an elegant set of APIs for talking to the database. ZenStack pushes its power further to the frontend.
E2E Type Safety
Type sharing between frontend and backend is effortless thanks to the code generation from a central schema.
Extensible Schema
Custom attributes and a plugin system give you the power to extend ZenStack's schema language to your needs.
Fullstack & Backend
Although ZenStack shines in full-stack development, it can also help you build just the backend more efficiently.
Framework Agnostic
ZenStack works with any Javascript framework. It's also easy to migrate your existing Prisma projects.
Uncompromised Performance
Access policies and validation rules are precompiled and pushed down to the database engine whenever possible.