drizzle-orm-d1
About
This skill provides type-safe ORM patterns for Cloudflare D1 databases using Drizzle. It covers schema definition, migrations management, type-safe queries, relations, and Workers integration while preventing common issues like transaction errors and foreign key constraint failures. Use it when building D1 database schemas, writing type-safe SQL queries, or managing database migrations with Drizzle Kit.
Quick Install
Claude Code
Recommended/plugin add https://github.com/jezweb/claude-skillsgit clone https://github.com/jezweb/claude-skills.git ~/.claude/skills/drizzle-orm-d1Copy and paste this command in Claude Code to install this skill
Documentation
Drizzle ORM for Cloudflare D1
Status: Production Ready ✅ Last Updated: 2025-10-24 Latest Version: [email protected], [email protected] Dependencies: cloudflare-d1, cloudflare-worker-base
Quick Start (10 Minutes)
1. Install Drizzle
npm install drizzle-orm
npm install -D drizzle-kit
# Or with pnpm
pnpm add drizzle-orm
pnpm add -D drizzle-kit
Why Drizzle?
- Type-safe queries with full TypeScript inference
- SQL-like syntax (no magic, no abstraction overhead)
- Serverless-ready (works perfectly with D1)
- Zero dependencies (except database driver)
- Excellent DX with IDE autocomplete
- Migrations that work with Wrangler
2. Configure Drizzle Kit
Create drizzle.config.ts in your project root:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './migrations',
dialect: 'sqlite',
driver: 'd1-http',
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
});
CRITICAL:
dialect: 'sqlite'- D1 is SQLite-baseddriver: 'd1-http'- For remote database access via HTTP API- Use environment variables for credentials (never commit these!)
3. Configure Wrangler
Update wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "your-database-id",
"preview_database_id": "local-db",
"migrations_dir": "./migrations" // ← Points to Drizzle migrations!
}
]
}
Why this matters:
migrations_dirtells Wrangler where to find SQL migration files- Drizzle generates migrations in
./migrations(from drizzle.config.tsout) - Wrangler can apply Drizzle-generated migrations with
wrangler d1 migrations apply
4. Define Your Schema
Create src/db/schema.ts:
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
});
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
});
// Define relations for type-safe joins
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
Key Points:
- Use
integerfor auto-incrementing IDs - Use
integerwithmode: 'timestamp'for dates (D1 doesn't have native date type) - Use
.$defaultFn()for dynamic defaults (not.default()for functions) - Define relations separately for type-safe joins
5. Generate & Apply Migrations
# Step 1: Generate SQL migration from schema
npx drizzle-kit generate
# Step 2: Apply to local database (for testing)
npx wrangler d1 migrations apply my-database --local
# Step 3: Apply to production database
npx wrangler d1 migrations apply my-database --remote
Why this workflow:
drizzle-kit generatecreates versioned SQL files in./migrations- Test locally first with
--localflag - Apply to production only after local testing succeeds
- Wrangler reads the migrations and applies them to D1
6. Query in Your Worker
Create src/index.ts:
import { drizzle } from 'drizzle-orm/d1';
import { users, posts } from './db/schema';
import { eq } from 'drizzle-orm';
export interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const db = drizzle(env.DB);
// Type-safe select with full inference
const allUsers = await db.select().from(users);
// Select with where clause
const user = await db
.select()
.from(users)
.where(eq(users.email, '[email protected]'))
.get(); // .get() returns first result or undefined
// Insert with returning
const [newUser] = await db
.insert(users)
.values({ email: '[email protected]', name: 'New User' })
.returning();
// Update
await db
.update(users)
.set({ name: 'Updated Name' })
.where(eq(users.id, 1));
// Delete
await db
.delete(users)
.where(eq(users.id, 1));
return Response.json({ allUsers, user, newUser });
},
};
CRITICAL:
- Use
.get()for single results (returns first or undefined) - Use
.all()for all results (returns array) - Import operators from
drizzle-orm:eq,gt,lt,and,or, etc. .returning()works with D1 (returns inserted/updated rows)
The Complete Setup Process
Step 1: Install Dependencies
# Core dependencies
npm install drizzle-orm
# Dev dependencies
npm install -D drizzle-kit @cloudflare/workers-types
# Optional: For local development with SQLite
npm install -D better-sqlite3
Step 2: Environment Variables
Create .env (never commit this!):
# Get these from Cloudflare dashboard
CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_DATABASE_ID=your-database-id
CLOUDFLARE_D1_TOKEN=your-api-token
How to get these:
- Account ID: Cloudflare dashboard → Account Home → Account ID
- Database ID: Run
wrangler d1 create my-database(output includes ID) - API Token: Cloudflare dashboard → My Profile → API Tokens → Create Token
Step 3: Project Structure
my-project/
├── drizzle.config.ts # Drizzle Kit configuration
├── wrangler.jsonc # Wrangler configuration
├── src/
│ ├── index.ts # Worker entry point
│ └── db/
│ └── schema.ts # Database schema
├── migrations/ # Generated by drizzle-kit
│ ├── meta/
│ │ └── _journal.json
│ └── 0001_initial_schema.sql
└── package.json
Step 4: Configure TypeScript
Update tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true
}
}
Critical Rules
Always Do
✅ Use drizzle-kit generate for migrations - Never write SQL manually
✅ Test migrations locally first - Always use --local flag before --remote
✅ Define relations in schema - For type-safe joins and nested queries
✅ Use .get() for single results - Returns first row or undefined
✅ Use db.batch() for transactions - D1 doesn't support SQL BEGIN/COMMIT
✅ Use integer with mode: 'timestamp' for dates - D1 doesn't have native date type
✅ Use .$defaultFn() for dynamic defaults - Not .default() for functions
✅ Set migrations_dir in wrangler.jsonc - Points to ./migrations
✅ Use environment variables for credentials - Never commit API keys
✅ Import operators from drizzle-orm - eq, gt, and, or, etc.
Never Do
❌ Never use SQL BEGIN TRANSACTION - D1 requires batch API (see Known Issue #1)
❌ Never mix wrangler d1 migrations apply and drizzle-kit migrate - Use Wrangler only
❌ Never use drizzle-kit push for production - Use generate + apply workflow
❌ Never forget to apply migrations locally first - Always test with --local
❌ Never commit drizzle.config.ts with hardcoded credentials - Use env vars
❌ Never use .default() for function calls - Use .$defaultFn() instead
❌ Never rely on prepared statement caching - D1 doesn't cache like SQLite (see Known Issue #7)
❌ Never use traditional transaction rollback - Use error handling in batch (see Known Issue #8)
❌ Never mix wrangler.toml and wrangler.jsonc - Use wrangler.jsonc consistently (see Known Issue #12)
Known Issues Prevention
This skill prevents 12 documented issues:
Issue #1: D1 Transaction Errors
Error: D1_ERROR: Cannot use BEGIN TRANSACTION
Source: https://github.com/drizzle-team/drizzle-orm/issues/4212
Why It Happens:
Drizzle tries to use SQL BEGIN TRANSACTION statements, but Cloudflare D1 raises a D1_ERROR requiring use of state.storage.transaction() APIs instead. Users cannot work around this error as Drizzle attempts to use BEGIN TRANSACTION when using bindings in Workers.
Prevention: Use D1's batch API instead of Drizzle's transaction API:
// ❌ DON'T: Use traditional transactions
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: '[email protected]', name: 'Test' });
await tx.insert(posts).values({ title: 'Post', content: 'Content', authorId: 1 });
});
// ✅ DO: Use D1 batch API
await db.batch([
db.insert(users).values({ email: '[email protected]', name: 'Test' }),
db.insert(posts).values({ title: 'Post', content: 'Content', authorId: 1 }),
]);
Template: See templates/transactions.ts
Issue #2: Foreign Key Constraint Failures
Error: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT
Source: https://github.com/drizzle-team/drizzle-orm/issues/4089
Why It Happens:
When generating migrations for Cloudflare D1, Drizzle-ORM uses the statement PRAGMA foreign_keys = OFF; which causes migrations to fail when executed. If tables have data and new migrations are generated, they fail with foreign key errors.
Prevention:
- Always define foreign keys in schema with proper cascading:
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
authorId: integer('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }), // ← Cascading deletes
});
- Ensure correct migration order (parent tables before child tables)
- Test migrations locally before production
Template: See templates/schema.ts
Issue #3: Module Import Errors in Production
Error: Error: No such module "wrangler"
Source: https://github.com/drizzle-team/drizzle-orm/issues/4257
Why It Happens: When using OpenNext, Drizzle, and D1, users encounter "Error: No such module 'wrangler'" which works locally but fails when deployed to Cloudflare Workers. This affects Next.js projects deployed to Cloudflare.
Prevention:
- Don't import from
wranglerpackage in runtime code - Use correct D1 import:
import { drizzle } from 'drizzle-orm/d1' - Configure bundler to externalize Wrangler if needed
Template: See templates/cloudflare-worker-integration.ts
Issue #4: D1 Binding Not Found
Error: TypeError: Cannot read property 'prepare' of undefined or env.DB is undefined
Why It Happens:
Missing or incorrect wrangler.jsonc configuration. The binding name in code doesn't match the binding name in config.
Prevention: Ensure binding names match exactly:
// wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB", // ← Must match env.DB in code
"database_name": "my-database",
"database_id": "your-db-id"
}
]
}
// src/index.ts
export interface Env {
DB: D1Database; // ← Must match binding name
}
export default {
async fetch(request: Request, env: Env) {
const db = drizzle(env.DB); // ← Accessing the binding
// ...
},
};
Reference: See references/wrangler-setup.md
Issue #5: Migration Apply Failures
Error: Migration failed to apply: near "...": syntax error
Why It Happens: Syntax errors in generated SQL, conflicting migrations, or applying migrations out of order.
Prevention:
- Always test migrations locally first:
npx wrangler d1 migrations apply my-database --local
-
Review generated SQL in
./migrationsbefore applying -
If migration fails, delete it and regenerate:
rm -rf migrations/
npx drizzle-kit generate
Reference: See references/migration-workflow.md
Issue #6: Schema TypeScript Inference Errors
Error: Type instantiation is excessively deep and possibly infinite
Why It Happens: Complex circular references in relations cause TypeScript to fail type inference.
Prevention: Use explicit type annotations in relations:
import { InferSelectModel } from 'drizzle-orm';
// Define types explicitly
export type User = InferSelectModel<typeof users>;
export type Post = InferSelectModel<typeof posts>;
// Use explicit types in relations
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
Reference: See references/schema-patterns.md
Issue #7: Prepared Statement Caching Issues
Error: Stale or incorrect results from queries
Why It Happens: Developers expect D1 to cache prepared statements like traditional SQLite, but D1 doesn't maintain statement caches between requests.
Prevention:
Always use .all(), .get(), or .run() methods correctly:
// ✅ Correct: Use .all() for arrays
const users = await db.select().from(users).all();
// ✅ Correct: Use .get() for single result
const user = await db.select().from(users).where(eq(users.id, 1)).get();
// ❌ Wrong: Don't rely on caching behavior
const stmt = db.select().from(users); // Don't reuse across requests
Template: See templates/prepared-statements.ts
Issue #8: Transaction Rollback Patterns
Error: Transaction doesn't roll back on error
Why It Happens: D1 batch API doesn't support traditional transaction rollback. If one statement in a batch fails, others may still succeed.
Prevention: Implement error handling with manual cleanup:
try {
const results = await db.batch([
db.insert(users).values({ email: '[email protected]', name: 'Test' }),
db.insert(posts).values({ title: 'Post', content: 'Content', authorId: 1 }),
]);
// Both succeeded
} catch (error) {
// Manual cleanup if needed
console.error('Batch failed:', error);
// Potentially delete partially created records
}
Template: See templates/transactions.ts
Issue #9: TypeScript Strict Mode Errors
Error: Type errors with strict: true in tsconfig.json
Why It Happens: Drizzle types can be loose, and TypeScript strict mode catches potential issues.
Prevention: Use explicit return types and assertions:
// ✅ Explicit return type
async function getUser(id: number): Promise<User | undefined> {
return await db.select().from(users).where(eq(users.id, id)).get();
}
// ✅ Type assertion when needed
const user = await db.select().from(users).where(eq(users.id, 1)).get() as User;
Issue #10: Drizzle Config Not Found
Error: Cannot find drizzle.config.ts
Why It Happens:
Wrong file location or incorrect file name. Drizzle Kit looks for drizzle.config.ts in the project root.
Prevention:
- File must be named exactly
drizzle.config.ts(notdrizzle.config.jsordrizzle-config.ts) - File must be in project root (not in
src/or subdirectory) - If using a different name, specify with
--configflag:
npx drizzle-kit generate --config=custom.config.ts
Issue #11: Remote vs Local D1 Confusion
Error: Changes not appearing in local development or production
Why It Happens:
Applying migrations to the wrong database. Forgetting to use --local flag during development or using it in production.
Prevention: Use consistent flags:
# Development: Always use --local
npx wrangler d1 migrations apply my-database --local
npx wrangler dev # Uses local database
# Production: Use --remote
npx wrangler d1 migrations apply my-database --remote
npx wrangler deploy # Uses remote database
Reference: See references/migration-workflow.md
Issue #12: wrangler.toml vs wrangler.jsonc
Error: Configuration not recognized or comments causing errors
Why It Happens: Mixing TOML and JSON config formats. TOML doesn't support comments the same way, and JSON doesn't support TOML syntax.
Prevention:
Use wrangler.jsonc consistently:
// wrangler.jsonc (supports comments!)
{
"name": "my-worker",
// This is a comment
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database"
}
]
}
Not:
# wrangler.toml (old format)
name = "my-worker"
Reference: See references/wrangler-setup.md
Configuration Files Reference
drizzle.config.ts (Full Example)
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
// Schema location (can be file or directory)
schema: './src/db/schema.ts',
// Output directory for migrations
out: './migrations',
// Database dialect
dialect: 'sqlite',
// D1 HTTP driver (for remote access)
driver: 'd1-http',
// Cloudflare credentials
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
// Verbose output
verbose: true,
// Strict mode
strict: true,
});
wrangler.jsonc (Full Example)
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
// D1 database bindings
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "your-production-db-id",
"preview_database_id": "local-db",
"migrations_dir": "./migrations" // Points to Drizzle migrations
}
],
// Node.js compatibility for Drizzle
"compatibility_flags": ["nodejs_compat"]
}
package.json Scripts
{
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"db:migrate:local": "wrangler d1 migrations apply my-database --local",
"db:migrate:remote": "wrangler d1 migrations apply my-database --remote"
}
}
Common Patterns
Pattern 1: CRUD Operations
import { drizzle } from 'drizzle-orm/d1';
import { users } from './db/schema';
import { eq, and, or, gt, lt, like } from 'drizzle-orm';
const db = drizzle(env.DB);
// Create
const [newUser] = await db
.insert(users)
.values({ email: '[email protected]', name: 'New User' })
.returning();
// Read (all)
const allUsers = await db.select().from(users).all();
// Read (single)
const user = await db
.select()
.from(users)
.where(eq(users.id, 1))
.get();
// Read (with conditions)
const activeUsers = await db
.select()
.from(users)
.where(and(
gt(users.createdAt, new Date('2024-01-01')),
like(users.email, '%@example.com')
))
.all();
// Update
await db
.update(users)
.set({ name: 'Updated Name' })
.where(eq(users.id, 1));
// Delete
await db
.delete(users)
.where(eq(users.id, 1));
Template: See templates/basic-queries.ts
Pattern 2: Relations & Joins
import { drizzle } from 'drizzle-orm/d1';
import { users, posts } from './db/schema';
import { eq } from 'drizzle-orm';
const db = drizzle(env.DB, { schema: { users, posts, usersRelations, postsRelations } });
// Nested query (requires relations defined)
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// Manual join
const usersWithPosts2 = await db
.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(posts.authorId, users.id))
.all();
// Filter nested queries
const userWithRecentPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
where: gt(posts.createdAt, new Date('2024-01-01')),
orderBy: [desc(posts.createdAt)],
limit: 10,
},
},
});
Template: See templates/relations-queries.ts
Pattern 3: Batch Operations (Transactions)
import { drizzle } from 'drizzle-orm/d1';
import { users, posts } from './db/schema';
const db = drizzle(env.DB);
// Batch insert
const results = await db.batch([
db.insert(users).values({ email: '[email protected]', name: 'User 1' }),
db.insert(users).values({ email: '[email protected]', name: 'User 2' }),
db.insert(users).values({ email: '[email protected]', name: 'User 3' }),
]);
// Batch with error handling
try {
const results = await db.batch([
db.insert(users).values({ email: '[email protected]', name: 'Test' }),
db.insert(posts).values({ title: 'Post', content: 'Content', authorId: 1 }),
]);
console.log('All operations succeeded');
} catch (error) {
console.error('Batch failed:', error);
// Manual cleanup if needed
}
Template: See templates/transactions.ts
Pattern 4: Prepared Statements
import { drizzle } from 'drizzle-orm/d1';
import { users } from './db/schema';
import { eq } from 'drizzle-orm';
const db = drizzle(env.DB);
// Prepared statement (reusable query)
const getUserById = db
.select()
.from(users)
.where(eq(users.id, sql.placeholder('id')))
.prepare();
// Execute with different parameters
const user1 = await getUserById.get({ id: 1 });
const user2 = await getUserById.get({ id: 2 });
Note: D1 doesn't cache prepared statements between requests like traditional SQLite.
Template: See templates/prepared-statements.ts
Using Bundled Resources
Scripts (scripts/)
check-versions.sh - Verify package versions are up to date
./scripts/check-versions.sh
Output:
Checking Drizzle ORM versions...
✓ drizzle-orm: 0.44.7 (latest)
✓ drizzle-kit: 0.31.5 (latest)
References (references/)
Claude should load these when you need specific deep-dive information:
- wrangler-setup.md - Complete Wrangler configuration guide (local vs remote, env vars)
- schema-patterns.md - All D1/SQLite column types, constraints, indexes
- migration-workflow.md - Complete migration workflow (generate, test, apply)
- query-builder-api.md - Full Drizzle query builder API reference
- common-errors.md - All 12 errors with detailed solutions
- links-to-official-docs.md - Organized links to official documentation
When to load:
- User asks about specific column types → load schema-patterns.md
- User encounters migration errors → load migration-workflow.md + common-errors.md
- User needs complete API reference → load query-builder-api.md
Advanced Topics
TypeScript Type Inference
import { InferSelectModel, InferInsertModel } from 'drizzle-orm';
import { users } from './db/schema';
// Infer types from schema
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;
// Usage
const user: User = await db.select().from(users).where(eq(users.id, 1)).get();
const newUser: NewUser = {
email: '[email protected]',
name: 'Test User',
// createdAt is optional (has default)
};
Migration Workflow Best Practices
Development:
- Make schema changes in
src/db/schema.ts - Generate migration:
npm run db:generate - Review generated SQL in
./migrations - Apply locally:
npm run db:migrate:local - Test in local dev:
npm run dev - Commit migration files to Git
Production:
- Deploy code:
npm run deploy - Apply migration:
npm run db:migrate:remote - Verify in production
Reference: See references/migration-workflow.md
Working with Dates
D1/SQLite doesn't have native date type. Use integer with timestamp mode:
export const events = sqliteTable('events', {
id: integer('id').primaryKey({ autoIncrement: true }),
// ✅ Use integer with timestamp mode
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
// ❌ Don't use text for dates
// createdAt: text('created_at'),
});
// Query with date comparisons
const recentEvents = await db
.select()
.from(events)
.where(gt(events.createdAt, new Date('2024-01-01')))
.all();
Dependencies
Required:
[email protected]- ORM runtime[email protected]- CLI tool for migrations
Optional:
[email protected]- For local SQLite development@cloudflare/[email protected]- TypeScript types
Skills:
- cloudflare-d1 - D1 database creation and raw SQL queries
- cloudflare-worker-base - Worker project structure and Hono setup
Official Documentation
- Drizzle ORM: https://orm.drizzle.team/
- Drizzle with D1: https://orm.drizzle.team/docs/connect-cloudflare-d1
- Drizzle Kit: https://orm.drizzle.team/docs/kit-overview
- Drizzle Migrations: https://orm.drizzle.team/docs/migrations
- GitHub: https://github.com/drizzle-team/drizzle-orm
- Cloudflare D1: https://developers.cloudflare.com/d1/
- Wrangler D1 Commands: https://developers.cloudflare.com/workers/wrangler/commands/#d1
- Context7 Library:
/drizzle-team/drizzle-orm-docs
Package Versions (Verified 2025-10-24)
{
"dependencies": {
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.5",
"@cloudflare/workers-types": "^4.20251014.0",
"better-sqlite3": "^12.4.1"
}
}
Production Example
This skill is based on production patterns from:
- Cloudflare Workers + D1: Serverless edge databases
- Drizzle ORM: Type-safe ORM used in production apps
- Errors: 0 (all 12 known issues prevented)
- Validation: ✅ Complete blog example (users, posts, comments)
Troubleshooting
Problem: D1_ERROR: Cannot use BEGIN TRANSACTION
Solution: Use db.batch() instead of db.transaction() (see Known Issue #1)
Problem: Foreign key constraint failed during migration
Solution: Define cascading deletes and ensure proper migration order (see Known Issue #2)
Problem: Migration not applying
Solution: Test locally first with --local flag, review generated SQL (see Known Issue #5)
Problem: TypeScript type errors with relations
Solution: Use explicit type annotations with InferSelectModel (see Known Issue #6)
Problem: env.DB is undefined
Solution: Check wrangler.jsonc binding names match code (see Known Issue #4)
Complete Setup Checklist
- Installed drizzle-orm and drizzle-kit
- Created drizzle.config.ts in project root
- Set up environment variables (CLOUDFLARE_ACCOUNT_ID, etc.)
- Updated wrangler.jsonc with D1 bindings and migrations_dir
- Defined schema in src/db/schema.ts
- Generated first migration with
drizzle-kit generate - Applied migration locally with
wrangler d1 migrations apply --local - Tested queries in Worker
- Applied migration to production with
--remote - Deployed Worker with
wrangler deploy - Verified all package versions are correct
- Set up npm scripts for common tasks
Questions? Issues?
- Check
references/common-errors.mdfor all 12 known issues - Verify all steps in the setup process
- Check official docs: https://orm.drizzle.team/docs/connect-cloudflare-d1
- Ensure D1 database is created and binding is configured
Token Savings: ~60% compared to manual setup Error Prevention: 100% (all 12 known issues documented and prevented) Ready for production! ✅
GitHub Repository
Related Skills
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
llamaguard
OtherLlamaGuard is Meta's 7-8B parameter model for moderating LLM inputs and outputs across six safety categories like violence and hate speech. It offers 94-95% accuracy and can be deployed using vLLM, Hugging Face, or Amazon SageMaker. Use this skill to easily integrate content filtering and safety guardrails into your AI applications.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
