Search docs

Jump between documentation pages.

Browse docs

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, and infers result types directly from your schema.

One request through Drizzle
  1. 01clientHTTP requestGET /users/:id
  2. 02zodValidated inputparams.id is a uuid
  3. 03drizzleTyped queryselect().from(users).where(eq(...))
  4. 04responseTyped body200 UserSchema | 404
Zod validates the request before your handler runs, Drizzle runs a SQL-like query off state.db with result types inferred from your schema, then the response schema checks the body on the way out.

1. Install

ts
pnpm add drizzle-orm postgres
pnpm add -D drizzle-kit

2. Define your schema

ts
// 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(),
});
ts
// 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! },
});
ts
pnpm drizzle-kit generate
pnpm drizzle-kit migrate

3. Create a Drizzle plugin

ts
// 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

ts
// 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

ts
// 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

ts
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
ts
// 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, MikroORM, 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.