ALWAYS readllms.txtfor curated documentation pages and examples.
ZQL on the Server
# ZQL on the Server
The Zero package includes utilities to run ZQL on the server directly against your upstream Postgres database.
This is useful for many reasons:
* It allows [Custom Mutators](https://zero.rocicorp.dev/docs/custom-mutators) to read data using ZQL to check permissions or invariants.
* You can use ZQL to implement standard REST endpoints, allowing you to share code with custom mutators.
* In the future ([but not yet implemented](#ssr)), this can support server-side rendering.
> `ZQLDatabase` currently does a read of your postgres schema before every transaction. This is fine for most usages, but for high scale it may become a problem. [Let us know](https://bugs.rocicorp.dev/issue/3799) if you need a fix for this.
## Creating a Database
To run ZQL on the database, you will create a `ZQLDatabase` instance. Zero ships with several built-in factories for popular Postgres bindings libraries.
### node-postgres
The industry standard [`node-postgres`](https://node-postgres.com/) library is supported via `zeroNodePg`:
```ts
import {Pool} from 'pg';
import {zeroNodePg} from '@rocicorp/zero/server/adapters/pg';
import {schema} from '../shared/schema';
const db = zeroNodePg(schema, new Pool({
connectionString: process.env.ZERO_UPSTREAM_DB!,
}));
```
You can also pass a `Client`:
```ts
import {Client} from 'pg';
import {zeroNodePg} from '@rocicorp/zero/server/adapters/pg';
import {schema} from '../shared/schema';
const client = new Client({
connectionString: process.env.ZERO_UPSTREAM_DB!,
});
await client.connect();
const db = zeroNodePg(schema, client);
```
### Postgres.js
The popular [`Postgres.js`](https://github.com/porsager/postgres) library is supported via `zeroPostgresJS`:
```ts
import postgres from 'postgres';
import {zeroPostgresJS} from '@rocicorp/zero/server/adapters/postgresjs';
import {schema} from '../shared/schema';
const db = zeroPostgresJS(
schema,
postgres(process.env.ZERO_UPSTREAM_DB!),
);
```
### Drizzle
Zero also includes an adapter for [Drizzle ORM](https://orm.drizzle.team/):
```ts
import {Pool} from 'pg';
import {drizzle} from 'drizzle-orm/node-postgres';
import {zeroDrizzle} from '@rocicorp/zero/server/adapters/drizzle';
import * as drizzleSchema from './drizzle-schema';
const drizzleDb = drizzle(pool, { schema: drizzleSchema });
const db = zeroDrizzle(schema, drizzleDb);
```
Within your custom mutators, you can access the underlying `drizzleDb` via `tx.wrappedTransaction`:
```ts
// server-mutators.ts
// Use a type helper for the server transaction type
import type {ServerTransaction} from '@rocicorp/zero/server';
import type {DrizzleTransaction} from '@rocicorp/zero/server/adapters/drizzle';
type ServerTx = ServerTransaction<
Schema,
DrizzleTransaction<typeof drizzleDb>
>;
async function createUser(
tx: ServerTx,
{ id, name }: { id: string; name: string },
) {
// this is then fully typed w/ drizzle
await tx.dbTransaction.wrappedTransaction
.insert(drizzleSchema.user)
.values({ id, name });
}
```
### Custom Database
To implement support for some other Postgres bindings library, you will implement the `DBConnection` interface.
See the implementations for the [existing adapters](https://github.com/rocicorp/mono/tree/6c27d88ce5a052fb8e1be90d072ddd0ebc13622a/packages/zero-server/src/adapters) for examples.
## Running ZQL
Once you have an instance of `ZQLDatabase`, use the `transaction()` method to run ZQL:
```ts
await db.transaction(async tx => {
// await tx.mutate...
// await tx.query...
// await myMutator(tx, ...args);
});
```
## SSR
Zero doesn't yet have the wiring setup in its bindings layers to really nicely support server-side rendering ([patches welcome though!](https://bugs.rocicorp.dev/issue/3491)).
For now, we don't recommend using Zero with SSR. Use your framework's recommended pattern to prevent SSR execution:
### Next.js
Add the `use client`[directive](https://nextjs.org/docs/app/api-reference/directives/use-client).
### SolidStart
Wrap components that use Zero with the [`clientOnly`](https://docs.solidjs.com/solid-start/reference/client/client-only)higher-order component.
The standard `clientOnly` pattern uses dynamic imports, but note that this approach (similar to [React's `lazy`](https://react.dev/reference/react/lazy)) works with any function returning a `Promise<{default: () => JSX.Element}>`. If code splitting is unnecessary, you can skip the dynamic import.
### TanStack Start
Use [React's `lazy`](https://react.dev/reference/react/lazy) for dynamic imports.
Or, for AI assistants
ALWAYS readllms.txtfor curated documentation pages and examples.
The Zero package includes utilities to run ZQL on the server directly against your upstream Postgres database.
This is useful for many reasons:
It allows Custom Mutators to read data using ZQL to check permissions or invariants.
You can use ZQL to implement standard REST endpoints, allowing you to share code with custom mutators.
To run ZQL on the database, you will create a ZQLDatabase instance. Zero ships with
several built-in factories for popular Postgres bindings libraries.
The industry standard node-postgres library is supported via zeroNodePg:
import{Pool}from'pg';import{zeroNodePg}from'@rocicorp/zero/server/adapters/pg';import{schema}from'../shared/schema';const db =zeroNodePg(schema,newPool({ connectionString: process.env.ZERO_UPSTREAM_DB!,}));
You can also pass a Client:
import{Client}from'pg';import{zeroNodePg}from'@rocicorp/zero/server/adapters/pg';import{schema}from'../shared/schema';const client =newClient({ connectionString: process.env.ZERO_UPSTREAM_DB!,});await client.connect();const db =zeroNodePg(schema, client);
Within your custom mutators, you can access the underlying drizzleDb via tx.wrappedTransaction:
// server-mutators.ts// Use a type helper for the server transaction typeimporttype{ServerTransaction}from'@rocicorp/zero/server';importtype{DrizzleTransaction}from'@rocicorp/zero/server/adapters/drizzle';typeServerTx= ServerTransaction< Schema, DrizzleTransaction<typeof drizzleDb>>;asyncfunctioncreateUser( tx: ServerTx,{ id, name }:{ id:string; name:string},){// this is then fully typed w/ drizzleawait tx.dbTransaction.wrappedTransaction
.insert(drizzleSchema.user).values({ id, name });}
Wrap components that use Zero with the
clientOnly
higher-order component.
The standard clientOnly pattern uses dynamic imports, but note that this
approach (similar to React's lazy)
works with any function returning a Promise<{default: () => JSX.Element}>. If
code splitting is unnecessary, you can skip the dynamic import.