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.
We try to make the enhanced Prisma Client as compatible as possible with the original Prisma Client, but there are still some limitations:
- No Sequential Operations Transaction
Enhanced Prisma CLient doesn't support sequential operations transaction. Use interactive transaction instead, or simply use the original Prisma Client.
- 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
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
. Theenhance
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.