Skip to content

Prisma ORM with Postgres

Contracts · Query API · Runnable example

Terminal window
bun add alchemy effect @prisma/orm-postgres arktype
bun add -d prisma

Cloudflare setup · Neon setup · Define a contract

src/prisma/validation.ts
import { configure } from "arktype/config";
configure({ jitless: true });

Import the bootstrap before the contract builder:

import "./validation.ts";
import { defineContract } from "alchemy/Prisma/ORM";

If your package uses "sideEffects": false, preserve this module:

{ "sideEffects": ["./src/prisma/validation.ts"] }

PSL-first projects skip this step.

src/db.ts
import * as Neon from "alchemy/Neon";
import * as Prisma from "alchemy/Prisma";
import * as Effect from "effect/Effect";
export const Db = Effect.gen(function* () {
const contract = yield* Prisma.Contract("contract");
const project = yield* Neon.Project("app-db");
const branch = yield* Neon.Branch("app-branch", { project });
yield* Prisma.Migrate("migrate", {
url: branch.connectionUri,
contract,
});
return branch;
});

Migration workflow

src/db.ts
import * as Cloudflare from "alchemy/Cloudflare";
export const Hyperdrive = Effect.gen(function* () {
const branch = yield* Db;
return yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", {
origin: branch.origin,
caching: { disabled: true },
});
});

Caching is disabled so reads immediately reflect writes. Hyperdrive guide.

src/api.ts
import * as Cloudflare from "alchemy/Cloudflare";
import * as PrismaPostgres from "alchemy/Prisma/ORM/Postgres";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
import { contract } from "./prisma/contract.ts";
import { Hyperdrive } from "./db.ts";
export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const conn = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
const db = yield* PrismaPostgres.Postgres(conn.connectionString, { contract });
return {
fetch: Effect.gen(function* () {
const users = yield* db.orm.public.User.all();
return yield* HttpServerResponse.json({ users });
}),
};
}).pipe(Effect.provide(Cloudflare.Hyperdrive.ConnectBinding)),
) {}

Connection lifecycle

import { makeDatabase } from "./prisma/generated/client.ts";
const conn = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
const db = yield* makeDatabase(conn.connectionString);

Generate the PSL client before compiling. The handler and binding layer stay the same.

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Neon from "alchemy/Neon";
import * as Prisma from "alchemy/Prisma";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import Api from "./src/api.ts";
export default Alchemy.Stack(
"PrismaWorker",
{
providers: Layer.mergeAll(
Cloudflare.providers(),
Neon.providers(),
Prisma.providers(),
),
state: Alchemy.localState(),
},
Effect.gen(function* () {
const api = yield* Api;
return { url: api.url };
}),
);
Terminal window
bun alchemy deploy
curl https://<your-worker>.workers.dev

This demo route is public. Add authentication before exposing private data.

Terminal window
bun alchemy destroy

Destroy removes the example’s Worker, Hyperdrive configuration, and Neon resources, including the database data.