Prisma ORM 8 is here.Read the docs

Databases

Give a service a Postgres database, either as a plain connection or typed by a Prisma ORM contract with managed migrations.

There are two ways for a service to get a Prisma Postgres database, depending on how much you want the framework to do. Both are dependencies: the service declares the need, the module provisions the resource, and your code receives the result from service.load().

rawPostgres(): bring your own client

Declare rawPostgres() when you want to build the client yourself. The dependency delivers connection config, { url }, and nothing else. In your server entry, build the client you already know (pg, Bun's SQL, an ORM):

src/auth/service.ts
import { compute, rawPostgres } from '@prisma/composer-prisma-cloud';

export default compute({
  name: 'auth',
  deps: { db: rawPostgres() },
  // ...
});
src/auth/server.ts
import { SQL } from 'bun';

const { db } = service.load();
const sql = new SQL({ url: db.url, max: 1, idleTimeout: 10 });

Those pool settings matter. Prisma Compute scales to zero and closes idle connections, so keep the pool small and reconnect-friendly. See Deploying.

The module that owns the database provisions it:

module.ts
const db = provision(rawPostgres({ name: 'database' }));
provision(authService, { deps: { db } });

postgres(): a Prisma ORM-typed database

If you want typed queries and managed migrations, make the database a Prisma ORM one. load() then returns { url, client }: the raw connection string, plus a client generated from your data contract. Queries like db.client.orm.public.Product.where({ id }).first() are compile-time checked, with no SQL strings and no row mapping. The client is constructed only on first access, so a service that brings its own Postgres client can still read db.url and keep contract-checked wiring and deploy-time migrations.

The schema workflow runs once per schema change, using the Prisma ORM CLI commands:

  1. Edit contract.prisma, your schema.
  2. Run contract emit to regenerate contract.json and contract.d.ts from it.
  3. Run migration plan to author the migration into migrations/.
  4. Deploy. The deploy applies migrations/ before the service starts. There is no CREATE TABLE IF NOT EXISTS anywhere in app code.

In your app, wrap the emitted contract once. Both the resource and every service that queries it reference that one value:

src/data.ts
import { dataContract } from '@prisma/composer-prisma-cloud/orm';
import type { Contract } from '../contract.d.ts';
import contractJson from '../contract.json' with { type: 'json' };

export const catalogData = dataContract<Contract>(contractJson);

You call postgres() on both sides of the wiring, and what you pass it tells the two apart. Passing the contract alone declares the dependency, the service saying what it queries:

deps: { db: postgres(catalogData) }

Passing an options object declares the resource. The module that owns the database provisions it, naming the prisma.config.ts path (relative to the module file) so the deploy can reload the emitted contract.json and find migrations/:

const db = provision(
  postgres({ name: 'database', contract: catalogData, config: './prisma.config.ts' }),
);

Because both ends share the contract value, the deploy refuses to wire a service against a database whose schema does not match. Deploy state records only the contract's identity; the full emitted contract is reloaded through prisma.config.ts at deploy time, and if contract.json is missing, unreadable, or no longer matches the declared contract, the deploy fails before touching the database.

For complete working versions, see examples/orm-demo (one service, one typed database) and examples/store (the full pattern inside a reusable Module) in the Composer repository.

Local databases

dev provisions real local Postgres instances for both kinds, so you can migrate and query them exactly as you would in production, with no cloud credentials. The data persists across restarts until you pass --fresh. See Local development.

Next steps

On this page