Skip to content

Commit 03bdf08

Browse files
authored
blog: prisma alternative (#520)
* blog: prisma alternative * update * update * update * update
1 parent 40e50ca commit 03bdf08

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

blog/prisma-alternative/cover.png

251 KB
Loading

blog/prisma-alternative/index.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
---
2+
title: 'ZenStack V3: The Perfect Prisma ORM Alternative'
3+
description: Why fighting Prisma's limitations when you can have a better choice?
4+
tags: [prisma, orm]
5+
authors: yiming
6+
date: 2025-11-30
7+
image: ./cover.png
8+
---
9+
10+
# ZenStack V3: The Perfect Prisma ORM Alternative
11+
12+
![Cover Image](cover.png)
13+
14+
[Prisma](https://www.prisma.io/) won the hearts of many 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:
15+
16+
- [Define type of content of Json field #3219](https://github.com/prisma/prisma/issues/3219)
17+
- [Support for Polymorphic Associations #1644](https://github.com/prisma/prisma/issues/1644)
18+
- [Soft deletes (e.g. deleted_at) #3398](https://github.com/prisma/prisma/issues/3398)
19+
20+
<!-- truncate -->
21+
22+
As a new challenger in this space, [ZenStack](https://zenstack.dev/v3) 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.
23+
24+
## A bit of history
25+
26+
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 `PrismaClient` 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.
27+
28+
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 [Kysely](https://kysely.dev)) and migrate away from Prisma. To ensure an easy migration path, we've made several essential compatibility commitments:
29+
30+
- The schema language remains compatible with (and in fact a superset of) Prisma Schema Language (PSL).
31+
- The resulting database schema remains unchanged, so no data migration is needed.
32+
- The query API stays compatible with PrismaClient.
33+
- Existing migration records continue to work without changes.
34+
35+
## Dual API from a single schema
36+
37+
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 [TypedSQL](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql) 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.
38+
39+
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:
40+
41+
```ts
42+
const users = await db.user.findMany({
43+
where: { age: { gt: 18 } },
44+
include: { posts: true },
45+
});
46+
```
47+
48+
Or, when your needs outgrow its power, you can seamlessly switch to Kysely's type-safe, fluent query builder API:
49+
50+
```ts
51+
const users = await db
52+
.$qb
53+
.selectFrom('User')
54+
.where('age', '>', 18)
55+
.leftJoin('Post', 'Post.authorId', 'User.id')
56+
.select(['User.*', 'Post.title'])
57+
.execute();
58+
```
59+
60+
For most applications, you'll never need to write a single line of SQL anymore.
61+
62+
## Battery included
63+
64+
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.
65+
66+
### Built-in access control
67+
68+
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:
69+
70+
**Schema:**
71+
```zmodel
72+
model Post {
73+
id Int @id
74+
title String
75+
published Boolean
76+
author User @relation(fields: [authorId], references: [id])
77+
authorId Int
78+
79+
@@deny('all', auth() == null) // deny anonymous access
80+
@@allow('all', auth() == author) // owner has full access
81+
@@allow('read', published) // published posts are publicly readable
82+
}
83+
```
84+
85+
**Query:**
86+
```ts
87+
async function handleRequest(req: Request) {
88+
// get the validated current user from your auth system
89+
const user = await getCurrentUser(req);
90+
91+
// create a user-bound ORM client
92+
const userDb = db.$setAuth(user);
93+
94+
// query with access policies automatically enforced
95+
const posts = await userDb.post.findMany();
96+
}
97+
```
98+
99+
### Strongly typed JSON
100+
101+
JSON columns are becoming increasingly popular in relational databases. Getting them typed is straightforward.
102+
103+
**Schema**:
104+
```zmodel
105+
model User {
106+
id Int @id
107+
profile Profile @json
108+
}
109+
110+
type Profile {
111+
age Int
112+
bio String
113+
}
114+
```
115+
116+
**Query**:
117+
```ts
118+
const user = await db.user.findFirstOrThrow();
119+
console.log(user.profile.age); // strongly typed
120+
```
121+
122+
### Polymorphic models
123+
124+
Ever felt the need to model an inheritance hierarchy in the database? Polymorphic models come to the rescue.
125+
126+
**Schema**:
127+
```zmodel
128+
model Content {
129+
id Int @id
130+
title String
131+
type String
132+
133+
// marks this model to be a polymorphic base with its
134+
// concrete type designated by the "type" field
135+
@@delegate(type)
136+
}
137+
138+
model Post extends Content {
139+
content String
140+
}
141+
142+
model Image extends Content {
143+
data Bytes
144+
}
145+
```
146+
147+
**Query**:
148+
```ts
149+
// a query with base automatically includes sub-model fields
150+
const content = await db.content.findFirstOrThrow();
151+
152+
// the returned type is a discriminated union
153+
if (content.type === 'Post') {
154+
// typed narrowed to `Post`
155+
console.log(content.content);
156+
} else if (content.type === 'Image') {
157+
// typed narrowed to `Image`
158+
console.log(content.data);
159+
}
160+
```
161+
162+
---
163+
164+
These are just some of the existing features. More cool stuff like soft deletes, audit trails, etc., will be added in the future.
165+
166+
## Extensible from the ground up
167+
168+
ZenStack ORM comprises three main pillars - the schema language, the CLI, and the ORM runtime. All three are designed with extensibility in mind.
169+
170+
- The schema language allows you to add custom attributes and functions to extend its semantics freely. E.g., you can add an `@encrypted` attribute to mark fields that need encryption.
171+
172+
```zmodel
173+
attribute @encrypted()
174+
175+
model User {
176+
id Int @id
177+
email String @unique @encrypted
178+
}
179+
```
180+
181+
- 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 `@encrypted` attribute and transparently encrypts/decrypts field values during writes/reads.
182+
183+
```ts
184+
const dbWithEncryption = db.$use(
185+
new EncryptionPlugin({
186+
algorithm: 'AES-256-CBC',
187+
key: process.env.ENCRYPTION_KEY,
188+
})
189+
);
190+
```
191+
192+
- 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.
193+
194+
```zmodel
195+
plugin erd {
196+
provider = './plugins/erd-generator'
197+
output = "./erd-diagram.md"
198+
}
199+
```
200+
201+
In fact, the access control feature mentioned earlier is entirely implemented with these extension points as a plugin package. The potential is limitless.
202+
203+
## Simpler, smaller footprint
204+
205+
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 `npm install --omit=dev`) is quite significant:
206+
207+
| ORM | "node_modules" Size |
208+
|------------|-------------------|
209+
| Prisma 7 | 224 MB |
210+
| ZenStack V3 | 33 MB |
211+
212+
A simpler code base also makes it easier for the community to navigate and contribute.
213+
214+
## Beyond ORM
215+
216+
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 [Query-as-a-Service](/docs/3.x/service) documentation if you're interested.
217+
218+
Furthermore, [frontend query hooks](/docs/3.x/service/client-sdk/tanstack-query/) (based on [TanStack Query](https://tanstack.com/query)) can be automatically derived, and they work seamlessly with the backend service.
219+
220+
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.
221+
222+
## Conclusion
223+
224+
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 [here](/docs/3.x/migrate-prisma). Make sure to join us in [Discord](https://discord.gg/Ykhr738dUe), and we'd love to hear your feedback!

0 commit comments

Comments
 (0)