# Use Drizzle ORM with DaloyJS

[Drizzle ORM](https://orm.drizzle.team) is a lightweight, TypeScript-native ORM with a SQL-like API. It runs everywhere DaloyJS does, including Cloudflare Workers, and infers result types directly from your schema.

**Diagram: One request through Drizzle**

1. **HTTP request** (client) - GET /users/:id
2. **Validated input** (zod) - params.id is a uuid
3. **Typed query** (drizzle) - select().from(users).where(eq(...))
4. **Typed body** (response) - 200 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.ts";

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

Add the `declare module` block to the same module that exports `db`, not to a separate `.d.ts` file. Declaration files are exempt from type-checking when `skipLibCheck` is on (the scaffolded default), so a broken import inside a `.d.ts` fails silently and `state.db` degrades to `any`.

```ts
// src/db/drizzle.ts (same module as the plugin above)
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.ts";
import { users } from "./db/schema.ts";

const app = new App();
app.register(drizzlePlugin);

const UserSchema = z.object({
  id: z.uuid(),
  email: z.email(),
  name: z.string().nullable(),
  createdAt: z.coerce.date(),
});

app.get(
  "/users/:id",
  {
    operationId: "getUser",
    request: { params: z.object({ id: z.uuid() }) },
    responses: {
      200: { description: "Found", body: UserSchema },
      404: { description: "Not found" },
    },
  },
  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.post(
  "/users",
  {
    operationId: "createUser",
    request: { body: z.object({ email: z.email(), name: z.string().optional() }) },
    responses: { 201: { description: "Created", body: UserSchema } },
  },
  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](/docs/orm/prisma), [TypeORM](/docs/orm/typeorm), [MikroORM](/docs/orm/mikro-orm), [Sequelize](/docs/orm/sequelize), or the [ODM overview](/docs/odm) if you are working with document databases.

Drizzle pairs cleanly with every host in the [database hosting overview](/docs/databases), including [Neon](/docs/databases/neon), [PlanetScale](/docs/databases/planetscale), [Turso](/docs/databases/turso), and [Cloudflare D1](/docs/databases/cloudflare-d1).

---

Source: https://daloyjs.dev/docs/orm/drizzle