Migrating From Prisma
Overview​
This guide will help you migrate an existing Prisma project to ZenStack v3. The process is made straightforward by the following design decisions:
- The ZModel schema language is a superset of Prisma Schema Language.
- The ORM provides a Prisma-compatible API.
- The migration engine is built on top of Prisma Migrate.
In the following sections, we'll cover various aspects of the migration process where care needs to be taken.
ZenStack v3 currently only supports PostgreSQL and SQLite databases.
Migration Steps​
1. Project dependencies​
ZenStack v3 doesn't depend on Prisma at runtime. Its CLI has a peer dependency on the prisma
package for migration related commands. The most straightforward way is to follow these steps:
-
Remove
prisma
and@prisma/client
from your project dependencies. -
Install ZenStack packages
- npm
- pnpm
- bun
- yarn
npm install --save-dev @zenstackhq/cli@next
npm install @zenstackhq/runtime@nextpnpm add --save-dev @zenstackhq/cli@next
pnpm add @zenstackhq/runtime@nextbun add --dev @zenstackhq/cli@next
bun add @zenstackhq/runtime@nextyarn add --dev @zenstackhq/cli@next
yarn add @zenstackhq/runtime@next -
Install a database driver
ZenStack doesn't bundle database drivers. Install the appropriate driver for your database:
- PostgreSQL
- SQLite
- npm
- pnpm
- bun
- yarn
npm install --save-dev @types/pg
npm install pgpnpm add --save-dev @types/pg
pnpm add pgbun add --dev @types/pg
bun add pgyarn add --dev @types/pg
yarn add pg- npm
- pnpm
- bun
- yarn
npm install --save-dev @types/better-sqlite3
npm install better-sqlite3pnpm add --save-dev @types/better-sqlite3
pnpm add better-sqlite3bun add --dev @types/better-sqlite3
bun add better-sqlite3yarn add --dev @types/better-sqlite3
yarn add better-sqlite3
You don't need to explicitly install prisma
package because the @zenstackhq/cli
has a peer dependency on it.
2. Migrate your schema​
If you have a single schema.prisma
file, you can move and rename it to zenstack/schema.zmodel
. No change should be necessary because every valid Prisma schema is also a valid ZModel schema. The only optional change you can consider making is to remove the Prisma client generator block since it doesn't have any effect in ZenStack:
generator client {
provider = "prisma-client-js"
}
If you use Prisma's multi-schema feature, you'll need to explicitly use the import
statement to merge related schema files into a whole graph. See Multi-file Schema for details.
3. Update generation command​
In your package.json
scripts, replace the prisma generate
command with zen generate
.
{
"scripts": {
"generate": "zen generate"
}
}
4. Update database client instantiation​
Replace new PrismaClient()
with new ZenStackClient(schema, ...)
where schema
is imported from the TypeScript code generated by the zen generate
command.
- PostgreSQL
- SQLite
import { ZenStackClient } from '@zenstackhq/runtime';
import { schema } from '@/zenstack/schema';
import { PostgresDialect } from 'kysely';
import { Pool } from 'pg';
export const db = new ZenStackClient(schema, {
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
}),
});
import { ZenStackClient } from '@zenstackhq/runtime';
import { SqliteDialect } from 'kysely';
import SQLite from 'better-sqlite3';
import { schema } from '@/zenstack/schema';
export const db = new ZenStackClient(schema, {
dialect: new SqliteDialect({
database: new SQLite('./db.sqlite'),
}),
});
Since ZenStackClient
has a PrismaClient-compatible API, you should not need to change your existing code that uses the database client.
5. Update type references​
Prisma generates many TypeScript types that you may have used in your code, such as User
, UserCreateArgs
, etc. ZenStack replicates some of the most useful types in its generated code.
For top-level model types like User
, you can import from the models.ts
file. For other detailed input types like UserCreateArgs
, import them from the input.ts
file.
6. Update migration command​
In your package.json
scripts, replace the Prisma db
and migrate
commands with ZenStack equivalents:
{
"scripts": {
"db:push": "zen db push",
"migrate:dev": "zen migrate dev",
"migrate:deploy": "zen migrate deploy"
}
}
Other Considerations​
Now, let's check the areas that are less straightforward to migrate.
Prisma custom generators​
ZenStack has its own CLI plugin system and doesn't support Prisma custom generators. However, you can continue running them with the following two steps:
-
Use the @core/prisma plugin to generate a Prisma schema from ZModel.
plugin prisma {
provider = '@core/prisma'
output = './schema.prisma'
} -
Run a
prisma generate
command afterzen generate
with the prisma schema as input.{
"scripts": {
"generate": "zen generate && prisma generate --schema=zenstack/schema.prisma"
}
}
Prisma client extensions​
ZenStack has its own runtime plugin mechanism and doesn't plan to be compatible with Prisma client extensions. However, there are easy migration paths for the two most popular types of Prisma client extensions.
1. Query extension
Query extension allows you to intercept ORM query calls.
Suppose you have an extension like:
const extPrisma = prisma.$extends({
query: {
user: {
async findMany({ model, operation, args, query }) {
// take incoming `where` and set `age`
args.where = { ...args.where, age: { gt: 18 } }
return query(args)
},
},
},
});
You can replace it with an equivalent ZenStack plugin:
const extDb = db.$use({
id: 'my-plugin',
onQuery: {
user: {
async findMany({ model, operation, args, proceed }) {
// take incoming `where` and set `age`
args.where = { ...args.where, age: { gt: 18 } }
return proceed(args)
},
},
},
});
You can also use the special $allModels
and $allOperations
keys to apply the plugin to all models and operations, like when using Prisma client extensions.
2. Result extension
Result extension allows you to add custom fields to query results. ZenStack provides a mechanism that achieves the same goal but a lot more powerful.
Suppose you have a result extension like:
const extPrisma = prisma.$extends({
result: {
user: {
fullName: {
// the dependencies
needs: { firstName: true, lastName: true },
compute(user) {
// the computation logic
return `${user.firstName} ${user.lastName}`
},
},
},
},
});
You can replace it with a ZenStack computed field, which involves changes in ZModel and database client instantiation.
model User {
...
firstName String
lastName String
fullName String @computed
}
export const db = new ZenStackClient(schema, {
...,
computedFields: {
User: {
// SQL: CONCAT(firstName, ' ', lastName)
fullName: (eb) => eb.fn('concat', ['firstName', eb.val(' '), 'lastName'])
},
},
});
A key difference is that ZenStack's computed fields are evaluated on the database side, which much more efficient and flexible than client-side computation. Read more in the Computed Fields documentation.