Use Drizzle ORM with DaloyJS
Drizzle ORM is a lightweight, TypeScript-native ORM with a SQL-like API. It runs everywhere DaloyJS does — including Cloudflare Workers and Vercel Edge — and infers result types directly from your schema.
1. Install
pnpm add drizzle-orm postgres
pnpm add -D drizzle-kit2. Define your schema
// src/db/schema.ts
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(),
email: text("email").notNull().unique(),
name: text("name"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: { url: process.env.DATABASE_URL! },
});pnpm drizzle-kit generate
pnpm drizzle-kit migrate3. Create a Drizzle plugin
// src/db/drizzle.ts
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import type { App } from "@daloyjs/core";
import * as schema from "./schema";
const client = postgres(process.env.DATABASE_URL!, { max: 10, prepare: false });
export const db = drizzle(client, { schema });
export const drizzlePlugin = {
name: "drizzle",
async register(app: App) {
app.decorate("db", db);
app.onClose(async () => {
await client.end({ timeout: 5 });
});
},
};4. Augment app state types
// src/types/state.d.ts
import type { db } from "../db/drizzle";
declare module "@daloyjs/core" {
interface AppState {
db: typeof db;
}
}5. Use it in routes
// src/server.ts
import { z } from "zod";
import { eq, sql } from "drizzle-orm";
import { App } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
import { drizzlePlugin } from "./db/drizzle";
import { users } from "./db/schema";
const app = new App();
app.register(drizzlePlugin);
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().nullable(),
createdAt: z.coerce.date(),
});
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.select().from(users).where(eq(users.id, params.id)).limit(1);
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 }) => {
const [created] = await state.db.insert(users).values(body).returning();
return { status: 201, body: created };
},
});
await app.ready();
serve(app, { port: 3000 });Transactions
handler: async ({ body, state }) => {
const order = await state.db.transaction(async (tx) => {
const [created] = await tx.insert(orders).values(body).returning();
await tx
.update(inventory)
.set({ stock: sql`${inventory.stock} - ${body.qty}` })
.where(eq(inventory.sku, body.sku));
return created;
});
return { status: 201, body: order };
}Edge runtimes
Drizzle is the easiest path to running DaloyJS against a real database on the edge. Pick a driver:
- Cloudflare Workers + D1:
drizzle-orm/d1 - Neon (Postgres) on any edge:
drizzle-orm/neon-http - PlanetScale (MySQL):
drizzle-orm/planetscale-serverless
// Cloudflare Workers + D1
import { drizzle } from "drizzle-orm/d1";
export default {
fetch(req: Request, env: Env, ctx: ExecutionContext) {
const db = drizzle(env.DB);
// app.decorate("db", db) per request, or build the App per-request.
return app.fetch(req, env, ctx);
},
};Compare with Prisma, TypeORM, Sequelize, or the ODM overview if you are working with document databases.
Drizzle pairs cleanly with every host in the database hosting overview, including Neon, PlanetScale, Turso, and Cloudflare D1.