Skip to main content
Version: 2.0 🚧

Enhanced Prisma Client

Here we are, the most exciting part of ZenStack.

In the previous chapters, we've been focusing on the design-time concepts: the ZModel language and the zenstack CLI. Now, let's get to ZenStack's power at runtime. You'll understand why we say ZenStack ⚡️supercharges⚡️ Prisma.

What Is Enhanced Prisma Client?

An enhanced Prisma Client is a transparent proxy that wraps a regular Prisma Client instance. It has the same API as the original Prisma Client but adds additional behaviors by intercepting API calls. The added behaviors include:

  • Enforcing access policies
  • Data validation
  • Hashing passwords
  • Omitting fields from query results

More will come in the future.

Creating an enhanced Prisma Client is easy, just call the enhance API with a regular Prisma Client:

import { PrismaClient } from '@prisma/client';
import { enhance } from '@zenstackhq/runtime';

const prisma = new PrismaClient();
const db = enhance(prisma);

// db has the same typing as prisma
await db.user.findMany();
await db.user.create({ data: { email: 'zen@stack.dev'} });

In a real-world application, you'll usually call enhance with an extra context argument to provide the current user identity, so that the access policy engine knows which user is making the call.

import { getSessionUser } from './auth';

// the `getSessionUser` implementation depends on your authentication solution
const db = enhance(prisma, { user: getSessionUser() });

We'll get to that in detail in the next chapter.

A few extra notes about enhanced Prisma Client:

  • Creating an enhanced client is cheap

    It doesn't cause new database connections to be made. It's common to create a new enhanced Prisma Client per request.

  • Using Prisma Client Extensions with enhanced client

    Enhanced Prisma Client can work with Prisma Client Extensions with some caveats. Please refer to Using With Prisma Client Extensions for more details.

  • Using the original client and an enhanced one together

    You can use both in your application. For example, you may want to use an enhanced client in part of the logic where you want access policy enforcement, while using the original client where you need unrestricted access to the database.

Limitations

We try to make the enhanced Prisma Client as compatible as possible with the original Prisma Client, but there are still some limitations:

  1. No Sequential Operations Transaction

Enhanced Prisma CLient doesn't support sequential operations transaction. Use interactive transaction instead, or simply use the original Prisma Client.

  1. Raw SQL APIs Are Not Enhanced

Although you can call raw sql APIs like $queryRaw or $executeRaw, these APIs are not enhanced, so their behavior is the same as the original Prisma Client. It means that, for example, if you use @omit to mark a field to be dropped on return:

model User {
...
password String @omit
}

If you query via $queryRaw, the password field will still be returned.

You should fall back to using the original Prisma Client in such cases.

Enhancement Kinds

ZenStack can enhance Prisma Client in several ways. When you call the enhance API to create an enhanced client, all enhancement kinds are enabled by default. You can use the kinds option to fine tune which ones to enable:

const db = enhance(prisma, { user: getSessionUser() }, { kinds: ['policy'] });

See here for more details.

🛠️ Using Enhanced Prisma Client In REPL

We saw in the previous chapter that in the REPL environment, you can use the built-in prisma variable to access Prisma Client directly. Another variable named db gives you access to an enhanced Prisma Client.

Let's try it out:

npx zenstack repl
db.user.findMany();
[]

It works but gives an empty array. Why? With an enhanced Prisma Client, all operations are denied by default unless you explicitly open them up with access policies. Let's see how to do that in the next chapter.

Inner Workings

info

This part is for those interested in the inner workings of ZenStack. It's not necessary to understand it to use ZenStack.

If you know the inner workings of Prisma Client, you'll find ZenStack shares some similarities.. When zenstack generate is run, besides generating the prisma/schema.prisma file, it also runs several other plugins that transform different pieces of information in the ZModel into Javascript code that can be efficiently loaded and executed at runtime. The enhance API from@zenstackhq/runtime relies on the generated Javascript modules to get its job done.

  • enhance

    Contains the function that enhances a PrismaClient. The enhance API from @zenstackhq/runtime simply reexports this function.

  • model-meta

    Lightweight representation of ZModel's AST.

  • policy

    Partial Prisma query input objects compiled from access policy expressions.

  • zod/**

    Zod schemas for validating input data according to ZModel.

The generation by default outputs to the node_modules/.zenstack folder. You can pass a --output, -o CLI switch when running zenstack generate to use a custom output location.

npx zenstack generate -o lib/zenstack

When using a custom output location, you can't use the enhance API from @zenstackhq/runtime directly. Instead, import directly from the output location:

import { enhance } from 'lib/zenstack/enhance';

Next

This chapter gave an abstract overview of the enhanced Prisma Client. In the following chapters, you'll see how each kind of enhancement helps simplify your development work.

Let's roll on.

Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below