Use Prisma with DaloyJS
Prisma is a schema-first ORM with first-class migrations and a generated, fully typed client. It pairs well with DaloyJS's contract-first routes: Zod validates the wire, Prisma validates the database.
1. Install
pnpm add @prisma/client @prisma/adapter-pg dotenv
pnpm add -D prisma
pnpm prisma init --datasource-provider postgresql2. Define your schema
// prisma/schema.prisma
datasource db {
provider = "postgresql"
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id String @id @default(uuid())
email String @unique
name String?
}// prisma.config.ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: { path: "prisma/migrations" },
datasource: {
url: env("DATABASE_URL"),
},
});Prisma's current prisma-client generator writes the client to the configured output path. Connection URLs live in prisma.config.ts, and application code imports PrismaClient from the generated path instead of @prisma/client.
pnpm prisma migrate dev --name init
pnpm prisma generate3. Create a Prisma plugin
Instantiate one PrismaClient per process, decorate the app, and disconnect on shutdown.
// src/db/prisma.ts
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../generated/prisma/client";
import type { App } from "@daloyjs/core";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
export const prisma = new PrismaClient({
adapter,
log: process.env.NODE_ENV === "production" ? ["error"] : ["query", "error"],
});
export const prismaPlugin = {
name: "prisma",
async register(app: App) {
await prisma.$connect();
app.decorate("db", prisma);
app.onClose(async () => {
await prisma.$disconnect();
});
},
};4. Augment app state types
// src/types/state.d.ts
import type { PrismaClient } from "../generated/prisma/client";
declare module "@daloyjs/core" {
interface AppState {
db: PrismaClient;
}
}5. Wire the plugin and route
// src/server.ts
import { z } from "zod";
import { App, secureHeaders, requestId } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
import { prismaPlugin } from "./db/prisma";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().nullable(),
});
const app = new App();
app.use(requestId());
app.use(secureHeaders());
app.register(prismaPlugin);
app.route({
method: "GET",
path: "/users/:id",
operationId: "getUser",
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
200: { description: "Found", body: UserSchema },
404: { description: "Not found" },
},
handler: async ({ params, state }) => {
const user = await state.db.user.findUnique({ where: { id: params.id } });
return user
? { status: 200, body: user }
: { status: 404, body: { type: "about:blank", title: "Not found", status: 404 } };
},
});
app.route({
method: "POST",
path: "/users",
operationId: "createUser",
request: { body: z.object({ email: z.string().email(), name: z.string().optional() }) },
responses: { 201: { description: "Created", body: UserSchema } },
handler: async ({ body, state }) => ({
status: 201,
body: await state.db.user.create({ data: body }),
}),
});
await app.ready();
serve(app, { port: 3000 });Transactions
Use $transaction for atomic units of work. Throwing inside the callback rolls back; a successful return commits.
handler: async ({ body, state }) => {
const order = await state.db.$transaction(async (tx) => {
const created = await tx.order.create({ data: { sku: body.sku, qty: body.qty } });
await tx.inventory.update({
where: { sku: body.sku },
data: { stock: { decrement: body.qty } },
});
return created;
});
return { status: 201, body: order };
}Edge runtimes
For Cloudflare Workers and Vercel Edge, set the generated client runtime for your target and use the appropriate Prisma Driver Adapter (Neon, PlanetScale, D1, etc.).
// prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
runtime = "vercel-edge" // use "workerd" for Cloudflare Workers
}
// src/db/prisma-edge.ts
import { PrismaClient } from "../generated/prisma/client";
import { PrismaNeon } from "@prisma/adapter-neon";
const adapter = new PrismaNeon({ connectionString: env.DATABASE_URL });
export const prisma = new PrismaClient({ adapter });Mapping errors to problem+json
import { Prisma } from "../generated/prisma/client";
import { HttpError } from "@daloyjs/core";
try {
return await state.db.user.create({ data: body });
} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
throw new HttpError(409, { title: "Email already in use" });
}
throw err;
}Continue with Drizzle, TypeORM, Sequelize, or the ODM overview for document databases.
For serverless or edge deployments, see the database hosting overview — Prisma supports Neon, PlanetScale, and Cloudflare D1 through Driver Adapters.