npm install drizzle-orm
npm install -D drizzle-kit vite-plugin-sql wranglerimport { sqliteTable, int, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
  id: int("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull()
});import { defineConfig } from 'drizzle-kit';
export default defineConfig({
  out: './do/users/migrations',
  schema: './do/users/schema.ts',
  dialect: 'sqlite',
  driver: 'durable-sqlite',
});npx drizzle-kit generate --config do/users/drizzle.config.ts
# Auto-creates:
# - do/users/migrations/0000_*.sql
# - do/users/migrations/meta/
# - do/users/migrations/migrations.jsimport { defineConfig } from "vite";
import sql from "vite-plugin-sql";
export default defineConfig({
  plugins: [sql()]
});import { drizzle } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers';
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import { users } from './schema';
import migrations from './migrations/migrations.js';
export class UsersDO extends DurableObject {
  db;
  constructor(ctx, env) {
    super(ctx, env);
    this.db = drizzle(ctx.storage);
    ctx.blockConcurrencyWhile(async () => {
      await migrate(this.db, migrations);
    });
  }
  async addUser(name) {
    const result = await this.db.insert(users)
      .values({ name })
      .returning();
    return result[0];
  }
  async getUsers() {
    return await this.db.select().from(users);
  }
}{
  "name": "my-app",
  "compatibility_date": "2025-04-04",
  "compatibility_flags": ["nodejs_compat"],
  "main": "./workers/app.ts",
  "durable_objects": {
    "bindings": [
      {
        "name": "USERS_DO",
        "class_name": "UsersDO"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["UsersDO"]
    }
  ]
}import { UsersDO } from "../do/users";
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const id = env.USERS_DO.idFromName("singleton");
    const stub = env.USERS_DO.get(id);
    if (url.pathname === "/api/users") {
      if (request.method === "POST") {
        const { name } = await request.json();
        const user = await stub.addUser(name);
        return Response.json(user);
      }
      const users = await stub.getUsers();
      return Response.json(users);
    }
    return new Response("Hello World");
  }
};
export { UsersDO };project/
├── do/                        # All Durable Objects
│   ├── users/                 # Users DO
│   │   ├── index.ts          # DO class
│   │   ├── schema.ts         # Drizzle schema
│   │   ├── drizzle.config.ts # Drizzle config
│   │   └── migrations/       # Auto-generated
│   │       ├── 0000_*.sql
│   │       ├── meta/
│   │       └── migrations.js
│   │
│   └── counter/              # Another DO example
│       ├── index.ts
│       ├── schema.ts
│       ├── drizzle.config.ts
│       └── migrations/
│
├── d1/                       # D1 Database (if needed)
│   ├── schema.ts
│   ├── drizzle.config.ts
│   └── migrations/
│
├── workers/
│   └── app.ts               # Main worker
│
├── vite.config.ts
└── wrangler.jsonc
npm run dev              # Start development
# Generate migrations for specific DO
npx drizzle-kit generate --config do/users/drizzle.config.ts
npx drizzle-kit generate --config do/counter/drizzle.config.ts
# Generate migrations for D1
npx drizzle-kit generate --config d1/drizzle.config.ts
# Studio for inspection
npx drizzle-kit studio --config do/users/drizzle.config.tsimport { drizzle } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers';
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import { counters } from './schema';
import migrations from './migrations/migrations.js';
export class CounterDO extends DurableObject {
  db;
  constructor(ctx, env) {
    super(ctx, env);
    this.db = drizzle(ctx.storage);
    ctx.blockConcurrencyWhile(async () => {
      await migrate(this.db, migrations);
    });
  }
  async increment(name) {
    // Implementation
  }
}import { defineConfig } from 'drizzle-kit';
export default defineConfig({
  out: './d1/migrations',
  schema: './d1/schema.ts',
  dialect: 'sqlite',
  driver: 'd1-http',  // Different driver for D1!
});import { UsersDO } from "../do/users";
import { CounterDO } from "../do/counter";
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    // Users DO
    if (url.pathname.startsWith("/api/users")) {
      const id = env.USERS_DO.idFromName("singleton");
      const stub = env.USERS_DO.get(id);
      // Handle users...
    }
    // Counter DO
    if (url.pathname.startsWith("/api/counter")) {
      const id = env.COUNTER_DO.idFromName("global");
      const stub = env.COUNTER_DO.get(id);
      // Handle counter...
    }
    // D1 queries
    if (url.pathname.startsWith("/api/products")) {
      const result = await env.DB.prepare("SELECT * FROM products").all();
      return Response.json(result);
    }
    return new Response("API Gateway");
  }
};
export { UsersDO, CounterDO };✅ Must Have:
- Each DO in its own folder under do/
- Separate drizzle.config.tsper DO
- nodejs_compatflag
- durable-sqlitedriver for DOs
- d1-httpdriver for D1
- Export all DO classes
❌ Avoid:
- Mixing DO and D1 schemas
- Single drizzle config for all
- Wrong driver types
- Missing exports
- Isolation: Each DO has its own schema and migrations
- Scalability: Easy to add new DOs or D1
- Clarity: Clear separation of concerns
- Maintainability: Independent migration management
- Type Safety: Each DO has its own types#