Skip to main content
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.
schema
Schema
required
Zero schema definition.
client
DrizzleDatabase
required
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.
schema
Schema
required
Zero schema definition.
client
PrismaClientLike
required
Prisma client instance.

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.
schema
Schema
required
Zero schema definition.
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.
schema
Schema
required
Zero schema definition.
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.
options
RunOptions
Query execution options.

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
required
Callback function receiving the transaction object.
transactionInput
TransactionProviderInput
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

FeatureDrizzlePrismapgpostgres.js
Type SafetyExcellentExcellentManualGood
Query BuilderYesYesNoTagged templates
Migrationsdrizzle-kitprisma migrateManualManual
Bundle SizeSmallLargeSmallSmall
PerformanceFastGoodFastestFast

Best Practices

  1. Choose the right adapter: Pick based on your existing stack and preferences
  2. Use ZQL for queries: Leverage reactive queries where possible
  3. Use raw SQL sparingly: Only for operations not supported by ZQL
  4. Handle transactions properly: Always use the provided transaction object
  5. Type your transactions: Use the helper types for better TypeScript support

Build docs developers (and LLMs) love