Zero provides adapters for popular PostgreSQL clients, allowing you to use your preferred database library while getting full ZQL query support and access to the underlying client for custom SQL.
Drizzle Adapter
Connect Zero to a Drizzle database.
import {Pool} from 'pg';
import {drizzle} from 'drizzle-orm/node-postgres';
import {zeroDrizzle} from '@rocicorp/zero/server/adapters/drizzle';
import {defineMutator, defineMutators} from '@rocicorp/zero';
import {z} from 'zod/mini';
const pool = new Pool({connectionString: process.env.ZERO_UPSTREAM_DB!});
const drizzleDb = drizzle(pool, {schema: drizzleSchema});
const zql = zeroDrizzle(schema, drizzleDb);
export const serverMutators = defineMutators({
user: {
create: defineMutator(
z.object({id: z.string(), name: z.string()}),
async ({tx, args}) => {
if (tx.location !== 'server') {
throw new Error('Server-only mutator');
}
// Use Drizzle client directly
await tx.dbTransaction.wrappedTransaction
.insert(drizzleSchema.user)
.values({id: args.id, name: args.name, status: 'active'});
},
),
},
});
zeroDrizzle
Wrap a Drizzle database for Zero ZQL.
Drizzle database instance from drizzle-orm/node-postgres.
Returns
Returns a ZQLDatabase<TSchema, DrizzleTransaction<TDrizzle>> with:
- Full ZQL query support via
zql.run()
- Transaction management via
zql.transaction()
- Access to Drizzle client via
tx.dbTransaction.wrappedTransaction
DrizzleTransaction
Helper type for typing server mutator transactions.
import type {DrizzleTransaction} from '@rocicorp/zero/server/adapters/drizzle';
import type {ServerTransaction} from '@rocicorp/zero/server';
type MyTransaction = ServerTransaction<typeof schema, DrizzleTransaction<typeof drizzleDb>>;
Prisma Adapter
Connect Zero to a Prisma client.
import {PrismaPg} from '@prisma/adapter-pg';
import {PrismaClient} from '@prisma/client';
import {zeroPrisma} from '@rocicorp/zero/server/adapters/prisma';
import {defineMutator, defineMutators} from '@rocicorp/zero';
import {z} from 'zod/mini';
const prisma = new PrismaClient({
adapter: new PrismaPg({connectionString: process.env.ZERO_UPSTREAM_DB!}),
});
const zql = zeroPrisma(schema, prisma);
export const serverMutators = defineMutators({
user: {
create: defineMutator(
z.object({id: z.string(), name: z.string()}),
async ({tx, args}) => {
if (tx.location !== 'server') {
throw new Error('Server-only mutator');
}
// Use Prisma client directly
await tx.dbTransaction.wrappedTransaction.user.create({
data: {
id: args.id,
name: args.name,
status: 'active',
},
});
},
),
},
});
zeroPrisma
Wrap a Prisma client for Zero ZQL.
Returns
Returns a ZQLDatabase<TSchema, PrismaTransaction<TClient>> with:
- Full ZQL query support via
zql.run()
- Transaction management via
zql.transaction()
- Access to Prisma client via
tx.dbTransaction.wrappedTransaction
PrismaTransaction
Helper type for typing server mutator transactions.
import type {PrismaTransaction} from '@rocicorp/zero/server/adapters/prisma';
import type {ServerTransaction} from '@rocicorp/zero/server';
type MyTransaction = ServerTransaction<typeof schema, PrismaTransaction<typeof prisma>>;
pg Adapter (node-postgres)
Connect Zero to a node-postgres Pool or Client.
import {Pool} from 'pg';
import {zeroNodePg} from '@rocicorp/zero/server/adapters/pg';
import {defineMutator, defineMutators} from '@rocicorp/zero';
import {z} from 'zod/mini';
const pool = new Pool({connectionString: process.env.ZERO_UPSTREAM_DB!});
const zql = zeroNodePg(schema, pool);
export const serverMutators = defineMutators({
user: {
create: defineMutator(
z.object({id: z.string(), name: z.string()}),
async ({tx, args}) => {
if (tx.location !== 'server') {
throw new Error('Server-only mutator');
}
// Use pg client directly
await tx.dbTransaction.wrappedTransaction.query(
'INSERT INTO "user" (id, name, status) VALUES ($1, $2, $3)',
[args.id, args.name, 'active'],
);
},
),
},
});
zeroNodePg
Wrap a node-postgres Pool, PoolClient, or Client for Zero ZQL.
pg
NodePgTransaction | string
required
pg Pool, PoolClient, Client, or connection string.
Returns
Returns a ZQLDatabase<TSchema, NodePgTransaction> with:
- Full ZQL query support via
zql.run()
- Transaction management via
zql.transaction()
- Access to pg client via
tx.dbTransaction.wrappedTransaction
NodePgTransaction
Helper type for typing server mutator transactions.
import type {NodePgTransaction} from '@rocicorp/zero/server/adapters/pg';
import type {ServerTransaction} from '@rocicorp/zero/server';
type MyTransaction = ServerTransaction<typeof schema, NodePgTransaction>;
postgres.js Adapter
Connect Zero to a postgres.js client.
import postgres from 'postgres';
import {zeroPostgresJS} from '@rocicorp/zero/server/adapters/postgresjs';
import {defineMutator, defineMutators} from '@rocicorp/zero';
import {z} from 'zod/mini';
const sql = postgres(process.env.ZERO_UPSTREAM_DB!);
const zql = zeroPostgresJS(schema, sql);
export const serverMutators = defineMutators({
user: {
create: defineMutator(
z.object({id: z.string(), name: z.string()}),
async ({tx, args}) => {
if (tx.location !== 'server') {
throw new Error('Server-only mutator');
}
// Use postgres.js client directly
await tx.dbTransaction.wrappedTransaction`
INSERT INTO "user" (id, name, status)
VALUES (${args.id}, ${args.name}, ${'active'})
`;
},
),
},
});
zeroPostgresJS
Wrap a postgres.js client for Zero ZQL.
pg
postgres.Sql<T> | string
required
postgres client or connection string.
Returns
Returns a ZQLDatabase<TSchema, PostgresJsTransaction<T>> with:
- Full ZQL query support via
zql.run()
- Transaction management via
zql.transaction()
- Access to postgres.js transaction via
tx.dbTransaction.wrappedTransaction
PostgresJsTransaction
Helper type for typing server mutator transactions.
import type {PostgresJsTransaction} from '@rocicorp/zero/server/adapters/postgresjs';
import type {ServerTransaction} from '@rocicorp/zero/server';
type MyTransaction = ServerTransaction<typeof schema, PostgresJsTransaction>;
ZQLDatabase
All adapters return a ZQLDatabase instance that provides:
run
Execute a ZQL query against PostgreSQL.
const todos = await zql.run(
zql.query.todo.where('complete', false)
);
query
Query<TTable, TSchema, TReturn>
required
ZQL query to execute.
transaction
Execute a callback within a database transaction.
await zql.transaction(async (tx) => {
await tx.mutate.todo.insert({
id: '1',
text: 'Buy milk',
complete: false,
});
const todos = await tx.run(
tx.query.todo.where('complete', false)
);
});
Callback function receiving the transaction object.
Optional transaction metadata for mutation tracking.
Accessing the Underlying Client
All adapters expose the underlying database client via tx.dbTransaction.wrappedTransaction, allowing you to:
- Execute raw SQL queries
- Use database-specific features
- Integrate with existing codebases
- Perform complex operations not supported by ZQL
defineMutator(
z.object({userId: z.string()}),
async ({tx, args}) => {
// Use ZQL for simple operations
const user = await tx.run(
tx.query.user.where('id', args.userId).one()
);
// Use raw SQL for complex operations
await tx.dbTransaction.wrappedTransaction.query(
'UPDATE user_stats SET last_login = NOW() WHERE user_id = $1',
[args.userId]
);
}
)
Comparison
| Feature | Drizzle | Prisma | pg | postgres.js |
|---|
| Type Safety | Excellent | Excellent | Manual | Good |
| Query Builder | Yes | Yes | No | Tagged templates |
| Migrations | drizzle-kit | prisma migrate | Manual | Manual |
| Bundle Size | Small | Large | Small | Small |
| Performance | Fast | Good | Fastest | Fast |
Best Practices
- Choose the right adapter: Pick based on your existing stack and preferences
- Use ZQL for queries: Leverage reactive queries where possible
- Use raw SQL sparingly: Only for operations not supported by ZQL
- Handle transactions properly: Always use the provided transaction object
- Type your transactions: Use the helper types for better TypeScript support