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 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 mutators.
  • In the future (but not yet implemented), this can support server-side rendering.

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.

// app/api/mutate/db-provider.ts
import {zeroDrizzle} from '@rocicorp/zero/server/adapters/drizzle'
import {schema} from '../../zero/schema.ts'

// pass a drizzle client instance. for example:
export const drizzleClient = drizzle(pool, {
  schema: drizzleSchema
})
export const dbProvider = zeroDrizzle(schema, drizzleClient)

// Register the database provider for type safety
declare module '@rocicorp/zero' {
  interface DefaultTypes {
    dbProvider: typeof dbProvider
  }
}

Within your mutators, you can access the underlying transaction via tx.dbTransaction.wrappedTransaction:

// mutators.ts
export const mutators = defineMutators({
  createUser: defineMutator(
    async ({tx, args: {id, name}}) => {
      if (tx.location === 'server') {
        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 for examples.

Running ZQL

Once you have an instance of ZQLDatabase, use the transaction() method to run ZQL:

await dbProvider.transaction(async tx => {
  // await tx.mutate...
  // await tx.query...
  // await myMutator.fn({tx, ctx, args})
})

SSR

Zero doesn't yet have the wiring setup in its bindings layers to really nicely support server-side rendering (patches welcome though!).

For now, we don't recommend using Zero with SSR. Use your framework's recommended pattern to prevent SSR execution:

import {lazy} from 'react'

// Use React lazy to defer loading the ZeroProvider
const ZeroProvider = lazy(() =>
  import('@rocicorp/zero/react').then(mod => ({
    default: mod.ZeroProvider
  }))
)

function Root() {
  return (
    <ZeroProvider>
      <App />
    </ZeroProvider>
  )
}