Drizzle ORM
Drizzle ORM Development Guidelines
Section titled “Drizzle ORM Development Guidelines”You are an expert in Drizzle ORM, TypeScript, and SQL database design with a focus on type safety and performance.
Core Principles
Section titled “Core Principles”- Drizzle embraces SQL - if you know SQL, you know Drizzle
- Schema-as-code serves as the single source of truth
- Type safety is enforced at compile time, catching errors before runtime
- Lightweight with minimal runtime overhead (~7.4kb min+gzip)
- Serverless-ready: works with Node.js, Bun, Deno, Cloudflare Workers
Schema Design
Section titled “Schema Design”Basic Table Definition
Section titled “Basic Table Definition”import { pgTable, serial, text, varchar, timestamp, boolean, integer } from "drizzle-orm/pg-core";
export const users = pgTable("users", { id: serial("id").primaryKey(), email: varchar("email", { length: 255 }).notNull().unique(), name: text("name"), isActive: boolean("is_active").default(true), createdAt: timestamp("created_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow(),});
export const posts = pgTable("posts", { id: serial("id").primaryKey(), title: varchar("title", { length: 255 }).notNull(), content: text("content"), authorId: integer("author_id").references(() => users.id), publishedAt: timestamp("published_at"), createdAt: timestamp("created_at").defaultNow(),});Schema Organization
Section titled “Schema Organization”You can organize schemas in multiple ways:
// Option 1: Single schema.ts file (recommended for smaller projects)// Option 2: Split by domain (recommended for larger projects)// src/db/schema/users.ts// src/db/schema/posts.ts// src/db/schema/index.ts (re-exports all)Naming Conventions
Section titled “Naming Conventions”Use the casing option for automatic camelCase to snake_case mapping:
import { drizzle } from "drizzle-orm/node-postgres";
const db = drizzle(pool, { casing: "snake_case", // Automatically maps camelCase to snake_case});Defining Relations
Section titled “Defining Relations”import { relations } from "drizzle-orm";
export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts),}));
export const postsRelations = relations(posts, ({ one }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], }),}));Adding Indexes
Section titled “Adding Indexes”import { pgTable, serial, varchar, index, uniqueIndex } from "drizzle-orm/pg-core";
export const users = pgTable( "users", { id: serial("id").primaryKey(), email: varchar("email", { length: 255 }).notNull(), name: varchar("name", { length: 255 }), }, (table) => [ uniqueIndex("email_idx").on(table.email), index("name_idx").on(table.name), ]);Database Connection
Section titled “Database Connection”PostgreSQL with node-postgres
Section titled “PostgreSQL with node-postgres”import { drizzle } from "drizzle-orm/node-postgres";import { Pool } from "pg";import * as schema from "./schema";
const pool = new Pool({ connectionString: process.env.DATABASE_URL,});
export const db = drizzle(pool, { schema });SQLite with better-sqlite3
Section titled “SQLite with better-sqlite3”import { drizzle } from "drizzle-orm/better-sqlite3";import Database from "better-sqlite3";import * as schema from "./schema";
const sqlite = new Database("sqlite.db");export const db = drizzle(sqlite, { schema });Turso/LibSQL
Section titled “Turso/LibSQL”import { drizzle } from "drizzle-orm/libsql";import { createClient } from "@libsql/client";import * as schema from "./schema";
const client = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN,});
export const db = drizzle(client, { schema });Query Patterns
Section titled “Query Patterns”Select Queries
Section titled “Select Queries”// Select all columnsconst allUsers = await db.select().from(users);
// Select specific columnsconst userEmails = await db.select({ email: users.email }).from(users);
// With conditionsimport { eq, and, or, gt, like } from "drizzle-orm";
const activeUsers = await db .select() .from(users) .where(eq(users.isActive, true));
const filteredUsers = await db .select() .from(users) .where( and( eq(users.isActive, true), like(users.email, "%@example.com") ) );Relational Queries
Section titled “Relational Queries”// Query with relations (requires schema with relations defined)const usersWithPosts = await db.query.users.findMany({ with: { posts: true, },});
// Nested relationsconst postsWithAuthor = await db.query.posts.findMany({ with: { author: { columns: { id: true, name: true, }, }, },});Insert Operations
Section titled “Insert Operations”// Single insertconst newUser = await db .insert(users) .values({ email: "user@example.com", name: "John Doe", }) .returning();
// Bulk insertawait db.insert(users).values([ { email: "user1@example.com", name: "User 1" }, { email: "user2@example.com", name: "User 2" },]);
// Upsert (insert or update on conflict)await db .insert(users) .values({ email: "user@example.com", name: "John" }) .onConflictDoUpdate({ target: users.email, set: { name: "John Updated" }, });Update Operations
Section titled “Update Operations”await db .update(users) .set({ name: "Jane Doe", updatedAt: new Date() }) .where(eq(users.id, 1));Delete Operations
Section titled “Delete Operations”await db.delete(users).where(eq(users.id, 1));Transactions
Section titled “Transactions”await db.transaction(async (tx) => { const [user] = await tx .insert(users) .values({ email: "user@example.com", name: "User" }) .returning();
await tx.insert(posts).values({ title: "First Post", authorId: user.id, });});Migrations
Section titled “Migrations”Generate Migrations
Section titled “Generate Migrations”# Generate migration based on schema changesnpx drizzle-kit generate
# Apply migrations to databasenpx drizzle-kit migrate
# Push schema directly (development only)npx drizzle-kit pushMigration Configuration
Section titled “Migration Configuration”import { defineConfig } from "drizzle-kit";
export default defineConfig({ schema: "./src/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL!, },});Type Safety Best Practices
Section titled “Type Safety Best Practices”Infer Types from Schema
Section titled “Infer Types from Schema”import { InferSelectModel, InferInsertModel } from "drizzle-orm";
// Infer types from table definitionsexport type User = InferSelectModel<typeof users>;export type NewUser = InferInsertModel<typeof users>;
// Use in application codefunction createUser(data: NewUser): Promise<User> { return db.insert(users).values(data).returning().then((r) => r[0]);}Strict TypeScript Configuration
Section titled “Strict TypeScript Configuration”Ensure strict mode is enabled in tsconfig.json:
{ "compilerOptions": { "strict": true, "strictNullChecks": true }}Performance Best Practices
Section titled “Performance Best Practices”Use Indexes Appropriately
Section titled “Use Indexes Appropriately”Always add indexes for columns used in WHERE clauses and JOINs:
export const orders = pgTable( "orders", { id: serial("id").primaryKey(), userId: integer("user_id").notNull(), status: varchar("status", { length: 50 }).notNull(), createdAt: timestamp("created_at").defaultNow(), }, (table) => [ index("user_id_idx").on(table.userId), index("status_idx").on(table.status), index("created_at_idx").on(table.createdAt), ]);Select Only Needed Columns
Section titled “Select Only Needed Columns”// Bad: Fetches all columnsconst users = await db.select().from(users);
// Good: Fetches only needed columnsconst userNames = await db .select({ id: users.id, name: users.name }) .from(users);Use Proper Pagination
Section titled “Use Proper Pagination”const page = 1;const pageSize = 20;
const paginatedUsers = await db .select() .from(users) .limit(pageSize) .offset((page - 1) * pageSize) .orderBy(users.createdAt);Avoid N+1 Queries
Section titled “Avoid N+1 Queries”// Bad: N+1 query patternconst users = await db.select().from(users);for (const user of users) { const posts = await db.select().from(posts).where(eq(posts.authorId, user.id));}
// Good: Use relational queries or joinsconst usersWithPosts = await db.query.users.findMany({ with: { posts: true },});Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”- Not defining indexes - Always add indexes for frequently queried columns
- Fetching too much data - Select only the columns you need
- Missing foreign key constraints - Define proper relationships in schema
- Manual migration modifications - Let drizzle-kit manage migration history
- Not using transactions - Wrap related operations in transactions for data integrity