### Install Drizzle Best Practices Skill Source: https://github.com/honra-io/drizzle-best-practices/blob/main/README.md Install the Drizzle best practices skill using the skills CLI. This makes the practices available to AI agents. ```bash npx skills add honra-io/drizzle-best-practices ``` -------------------------------- ### Inline Unique Constraint Example Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md Demonstrates how to define a simple unique constraint directly on a column definition. ```typescript email: varchar('email').unique(), ``` -------------------------------- ### Fetch Users with Their Posts Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-querying.md Use the `with` option to fetch all related posts for each user. This is a basic example of fetching nested data. ```typescript const usersWithPosts = await db.query.users.findMany({ with: { posts: true, }, }); // [{ id: 1, name: "Alice", posts: [{ id: 1, title: "Hello", ... }] }] ``` -------------------------------- ### Drizzle ORM Project Structure Source: https://github.com/honra-io/drizzle-best-practices/blob/main/CLAUDE.md Illustrates the recommended directory structure for a Drizzle ORM project, including the main skill file, navigation guide, and categorized reference files. ```tree drizzle-best-practices/ SKILL.md # Main skill file - read this first AGENTS.md # This navigation guide CLAUDE.md # Symlink to AGENTS.md references/ engine-postgres.md # Postgres-specific types, features, and idioms schema-*.md # Schema design patterns query-*.md # Query patterns relations-*.md # Relation definitions and relational queries migrations-*.md # Drizzle Kit configuration and workflow types-*.md # TypeScript type inference and custom types perf-*.md # Performance optimization driver-*.md # Driver setup and serverless configuration advanced-*.md # Dynamic queries, raw SQL, power-user patterns _sections.md # Full index of all reference files ``` -------------------------------- ### Inline Foreign Key Constraint Example Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md Shows how to define a simple foreign key constraint inline with the column definition, referencing another table's ID. ```typescript authorId: integer('author_id').references(() => users.id), ``` -------------------------------- ### Correct Table Definitions with Indexes and Constraints Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md This example demonstrates the correct way to define tables with Drizzle ORM, including unique indexes, foreign key references, and regular indexes for performance. ```typescript import { pgTable, integer, varchar, text, index, uniqueIndex } from 'drizzle-orm/pg-core'; import * as t from 'drizzle-orm/pg-core'; import { users } from './users'; export const users = pgTable( 'users', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), email: t.varchar('email', { length: 256 }).notNull(), username: t.varchar('username', { length: 64 }).notNull(), }, (table) => [ t.uniqueIndex('users_email_idx').on(table.email), t.uniqueIndex('users_username_idx').on(table.username), ] ); export const posts = pgTable( 'posts', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), title: t.varchar('title', { length: 256 }).notNull(), authorId: t.integer('author_id').notNull().references(() => users.id), categoryId: t.integer('category_id').notNull(), }, (table) => [ t.index('posts_author_id_idx').on(table.authorId), t.index('posts_category_id_idx').on(table.categoryId), ] ); ``` -------------------------------- ### Incorrect Table Definitions (No Indexes/Constraints) Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md This example shows a common mistake of defining tables without necessary indexes or constraints, leading to performance issues and data integrity problems. ```typescript import { pgTable, integer, varchar, text } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: integer().primaryKey().generatedAlwaysAsIdentity(), email: varchar('email', { length: 256 }).notNull(), // frequently queried but no index username: varchar('username', { length: 64 }).notNull(), // should be unique }); export const posts = pgTable('posts', { id: integer().primaryKey().generatedAlwaysAsIdentity(), title: varchar('title', { length: 256 }), authorId: integer('author_id').notNull(), // FK without index = slow joins categoryId: integer('category_id').notNull(), }); ``` -------------------------------- ### Implement Full-Text Search with tsvector and GIN Indexes Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Utilize Postgres's built-in full-text search capabilities by defining `tsvector` columns and creating GIN indexes. This snippet shows table definition and an example query. ```typescript export const articles = pgTable( 'articles', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), title: t.text('title').notNull(), body: t.text('body').notNull(), searchVector: t.text('search_vector'), }, (table) => [ t.index('articles_search_idx').using('gin', sql`to_tsvector('english', ${table.title} || ' ' || ${table.body})`), ] ); // Full-text search query await db.select().from(articles).where( sql`to_tsvector('english', ${articles.title} || ' ' || ${articles.body}) @@ plainto_tsquery('english', ${searchTerm})` ); ``` -------------------------------- ### Offset-based Pagination with Drizzle Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-dynamic-queries.md Implement simple offset-based pagination for retrieving data in chunks. Use `limit` and `offset` methods to control the number of records and their starting position. This method is suitable for smaller datasets. ```typescript async function getUsers(page: number, pageSize: number) { return db .select() .from(users) .limit(pageSize) .offset((page - 1) * pageSize) .orderBy(users.id); ``` -------------------------------- ### Table-Level Composite Unique Constraint Example Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md Illustrates defining a unique constraint that spans multiple columns using the table-level constraint declaration. ```typescript (table) => [ t.unique('unique_org_name').on(table.orgId, table.name), ] ``` -------------------------------- ### Filter, Order, and Limit Related Data Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-querying.md Apply `where`, `orderBy`, and `limit` options within the `with` clause to control the related data fetched for each primary record. This example filters posts by published status, orders them by creation date, limits to 10, and further limits comments. ```typescript const user = await db.query.users.findFirst({ where: eq(users.id, 1), with: { posts: { where: eq(posts.published, true), orderBy: [desc(posts.createdAt)], limit: 10, with: { comments: { limit: 3, orderBy: [desc(comments.createdAt)], }, }, }, }, }); ``` -------------------------------- ### Define Custom Column Types with customType Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/types-custom-types.md Implement customType for full control over value mapping between TypeScript and the database. This example defines a 'money' type storing cents as integers but exposing formatted strings. ```typescript import { customType, pgTable, integer, text, } from 'drizzle-orm/pg-core'; // A custom type that stores monetary values as integers (cents) in the DB // but exposes them as formatted strings in TypeScript const money = customType<{ data: string; driverData: number }>({ dataType() { return 'integer'; }, fromDriver(value: number): string { return (value / 100).toFixed(2); }, toDriver(value: string): number { return Math.round(parseFloat(value) * 100); }, }); export const products = pgTable('products', { id: integer().primaryKey().generatedAlwaysAsIdentity(), price: money('price').notNull(), }); // In TypeScript: product.price is string ("19.99") // In database: price is integer (1999) ``` -------------------------------- ### Define and Query Materialized Views Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Illustrates how to define and query materialized views in PostgreSQL using Drizzle ORM. Materialized views store precomputed results, which can significantly speed up complex queries. Includes an example of refreshing the view. ```typescript import { pgMaterializedView } from 'drizzle-orm/pg-core'; export const userStats = pgMaterializedView('user_stats').as((qb) => qb.select({ userId: posts.authorId, postCount: sql`count(*)`.mapWith(Number), }).from(posts).groupBy(posts.authorId) ); // Query the materialized view await db.select().from(userStats); // Refresh it (via raw SQL) await db.execute(sql`REFRESH MATERIALIZED VIEW user_stats`); ``` -------------------------------- ### Generate and Apply Migrations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Use this workflow for production deployments to create versioned SQL files and apply them in order. ```bash npx drizzle-kit generate npx drizzle-kit migrate ``` -------------------------------- ### Open Drizzle Studio Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Launches Drizzle Studio, a web-based tool for browsing and editing database data. ```bash npx drizzle-kit studio ``` -------------------------------- ### Basic PostgreSQL Configuration Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Sets up drizzle-kit for PostgreSQL, specifying the schema location and output directory for migrations. Ensure the `DATABASE_URL` environment variable is set. ```typescript import { defineConfig } from 'drizzle-kit'; export default defineConfig({ dialect: 'postgresql', schema: './src/db/schema.ts', out: './drizzle', dbCredentials: { url: process.env.DATABASE_URL!, }, }); ``` -------------------------------- ### postgres.js - Client Initialization Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md A lightweight and fast Postgres client suitable for both servers and serverless environments. Initialize Drizzle with the postgres.js client. ```typescript import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; const client = postgres(process.env.DATABASE_URL!); const db = drizzle({ client }); ``` -------------------------------- ### Incorrect Production Pattern: Using Push Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Avoid using `push` in production environments as it does not create migration files and lacks rollback capabilities. Always use `generate` + `migrate` for reproducible deployments. ```typescript // Wrong: running push in production // push doesn't create migration files and can't be rolled back // Use generate + migrate for any environment where you need reproducible deployments ``` -------------------------------- ### Catch Foreign Key Violations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-error-handling.md Handle 'foreign_key_violation' errors (code 23503) that occur when inserting a reference to a non-existent row or deleting a referenced row. This example throws a NotFoundError. ```typescript try { await db.insert(postsTable).values({ title: 'Hello', authorId: 999, // author doesn't exist }); } catch (error) { if (error instanceof DatabaseError && error.code === '23503') { // error.constraint — e.g. "posts_author_id_users_id_fk" // error.detail — e.g. 'Key (author_id)=(999) is not present in table "users".' throw new NotFoundError('Referenced author does not exist'); } throw error; } ``` -------------------------------- ### Vercel Serverless: Neon HTTP and Postgres.js with Pooler Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Two options for Vercel Serverless Functions: Neon HTTP for simplicity without transactions, or postgres.js with a pooler for full features. Choose based on your transaction requirements. ```typescript // Option 1: Neon HTTP (simplest, no transactions) import { neon } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-http'; const sql = neon(process.env.DATABASE_URL!); const db = drizzle({ client: sql }); // Option 2: postgres.js with pooler (full features) import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; const client = postgres(process.env.DATABASE_POOLER_URL!); const db = drizzle({ client }); ``` -------------------------------- ### Catch Unique Constraint Violations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-error-handling.md Catch 'unique_violation' errors (code 23505) when inserting or updating data that violates a unique constraint. This example shows how to re-throw a custom ConflictError. ```typescript import { DatabaseError } from 'pg'; // node-postgres try { const [user] = await db .insert(usersTable) .values({ email: 'alice@example.com', name: 'Alice' }) .returning(); return user; } catch (error) { if (error instanceof DatabaseError && error.code === '23505') { // error.constraint contains the constraint name, e.g. "users_email_unique" // error.detail contains specifics, e.g. 'Key (email)=(alice@example.com) already exists.' throw new ConflictError(`Email already in use`); } throw error; // re-throw unexpected errors } ``` -------------------------------- ### Drizzle ORM Reference File List Source: https://github.com/honra-io/drizzle-best-practices/blob/main/CLAUDE.md Lists the reference files available within the Drizzle ORM best practices documentation, covering various aspects of schema design, querying, relations, migrations, types, performance, drivers, and advanced patterns. ```text references/engine-postgres.md references/schema-table-definitions.md references/schema-column-types.md references/schema-indexes-constraints.md references/query-select-patterns.md references/query-mutations.md references/query-filters-operators.md references/query-error-handling.md references/relations-defining.md references/relations-querying.md references/migrations-config.md references/migrations-workflow.md references/types-inference.md references/types-custom-types.md references/perf-prepared-statements.md references/perf-batch-operations.md references/driver-postgres.md references/driver-serverless.md references/advanced-dynamic-queries.md references/advanced-sql-operator.md references/_sections.md ``` -------------------------------- ### node-postgres (pg) - Connection String Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Use this for long-running Node.js servers. Initialize Drizzle with a connection string. ```typescript import { drizzle } from 'drizzle-orm/node-postgres'; // Simplest: connection string const db = drizzle(process.env.DATABASE_URL!); ``` -------------------------------- ### Cloudflare Workers with Hyperdrive Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md For Cloudflare Workers, leverage Cloudflare Hyperdrive for connection pooling with Drizzle ORM. This example shows initializing the Drizzle client with the Hyperdrive connection string. ```typescript import { drizzle } from 'drizzle-orm/node-postgres'; export default { async fetch(request: Request, env: Env) { const db = drizzle(env.HYPERDRIVE.connectionString); const users = await db.select().from(usersTable); return Response.json(users); }, }; ``` -------------------------------- ### Database Credentials via Connection String Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Specifies database connection details using a single URL string. This is the most common method when using environment variables. ```typescript dbCredentials: { url: process.env.DATABASE_URL!, } ``` -------------------------------- ### Postgres.js with Connection Pooler Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Utilize postgres.js with a connection pooler for full features in serverless environments. Ensure you use the POOLER URL, not the direct connection URL, to manage connections effectively. ```typescript import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; // Use the POOLER URL — not the direct connection URL const client = postgres(process.env.DATABASE_POOLER_URL!); const db = drizzle({ client }); ``` -------------------------------- ### Incorrect SQL Generic Type Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-sql-operator.md Ensure you provide a generic type parameter (e.g., `sql`) for SQL expressions to get proper TypeScript typing. Omitting it results in an `unknown` type. ```typescript // Wrong: forgetting the generic type const result = await db.select({ count: sql`count(*)`, // type is unknown }).from(users); // Add sql to get proper typing ``` -------------------------------- ### Upgrade Migration Format Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Use this command to upgrade your migration folder to the latest format when Drizzle Kit introduces changes to its migration structure. ```bash npx drizzle-kit up ``` -------------------------------- ### Database Credentials via Individual Parameters Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Provides database connection details using individual parameters like host, port, user, password, and database name. Useful for specific configurations or when a connection string is not feasible. ```typescript dbCredentials: { host: 'localhost', port: 5432, user: 'postgres', password: 'password', database: 'mydb', ssl: true, } ``` -------------------------------- ### Incorrect Pattern - Direct URL in Serverless Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Do not use direct database URLs in serverless environments without a connection pooler. Each invocation will open a new connection, potentially exhausting limits. Use a pooler like PgBouncer, Supabase pooler, or Neon pooler instead. ```typescript // Wrong: using direct DB URL in serverless without a pooler // Each invocation opens a new connection, quickly exhausting the limit const db = drizzle(process.env.DIRECT_DATABASE_URL!); // Use a connection pooler (PgBouncer, Supabase pooler, Neon pooler) instead ``` -------------------------------- ### Querying PostgreSQL Arrays with Drizzle ORM Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Query array columns using PostgreSQL's native array operators within the Drizzle `sql` tag. Examples include checking for element existence and array overlap. ```typescript // Check if array contains a value await db.select().from(posts).where( sql`'typescript' = ANY(${posts.tags}) ); // Check if arrays overlap await db.select().from(posts).where( sql`${posts.tags} && ARRAY['typescript', 'javascript']` ); ``` -------------------------------- ### Logging All Queries Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Enable simple query logging by setting the `logger` option to `true` in the Drizzle configuration. ```typescript // Simple: log all queries to console const db = drizzle({ client: pool, logger: true }); ``` -------------------------------- ### Basic Prepared Statement Usage Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-prepared-statements.md Prepare a query once and execute it multiple times with different parameters. The statement name is required for PostgreSQL. ```typescript const db = drizzle(...); // Prepare once (at module scope or during initialization) const getUserById = db .select() .from(users) .where(eq(users.id, sql.placeholder('id'))) .prepare('get_user_by_id'); // name is required for PostgreSQL // Execute many times with different parameters const user1 = await getUserById.execute({ id: 1 }); const user2 = await getUserById.execute({ id: 2 }); const user3 = await getUserById.execute({ id: 3 }); ``` -------------------------------- ### Neon WebSocket - Serverless with Transactions Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Use this driver for serverless environments when multi-statement transactions are required. It utilizes WebSockets and requires configuring the WebSocket constructor. ```typescript import { Pool, neonConfig } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-serverless'; import ws from 'ws'; neonConfig.webSocketConstructor = ws; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const db = drizzle({ client: pool }); ``` -------------------------------- ### Neon HTTP Driver for Serverless Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Use the Neon HTTP driver for zero connection overhead in serverless environments. This driver treats each query as an independent HTTP request and does not support multi-statement transactions. ```typescript import { neon } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-http'; const sql = neon(process.env.DATABASE_URL!); const db = drizzle({ client: sql }); ``` -------------------------------- ### Supabase Integration with postgres.js Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Connect to Supabase using postgres.js. Ensure you use the pooler connection string provided by Supabase. ```typescript import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; // Use the pooler connection string, not the direct one const client = postgres(process.env.SUPABASE_DB_URL!); const db = drizzle({ client }); ``` -------------------------------- ### Select Specific Columns for Users and Posts Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-querying.md Use the `columns` option to specify which columns to fetch for the main entity and its relations, reducing data transfer. ```typescript const result = await db.query.users.findMany({ columns: { id: true, name: true, // email is NOT fetched }, with: { posts: { columns: { title: true, // content, authorId, etc. are NOT fetched }, }, }, }); ``` -------------------------------- ### Loading Environment Variables for Credentials Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Loads environment variables from a `.env` file before configuring drizzle-kit. This is essential for securely managing database credentials. ```typescript import { config } from 'dotenv'; import { defineConfig } from 'drizzle-kit'; config({ path: '.env' }); export default defineConfig({ dialect: 'postgresql', schema: './src/db/schema.ts', out: './drizzle', dbCredentials: { url: process.env.DATABASE_URL!, }, }); ``` -------------------------------- ### Check Migration State Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Verifies the consistency of your migration folder to ensure all migration files are in order and accounted for. ```bash npx drizzle-kit check ``` -------------------------------- ### Organize Tables Using Postgres Schemas Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Demonstrates how to use PostgreSQL schemas to organize your database tables with Drizzle ORM. This helps in managing larger databases by grouping related tables. ```typescript import { pgSchema } from 'drizzle-orm/pg-core'; const authSchema = pgSchema('auth'); export const authUsers = authSchema.table('users', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), email: t.text('email').notNull(), }); // Creates table in the "auth" schema: auth.users ``` -------------------------------- ### Incorrect Configuration: Hardcoding Credentials Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Never commit database credentials directly into your configuration file. Always use environment variables for security. ```typescript dbCredentials: { url: 'postgresql://admin:password123@localhost:5432/mydb', // Never commit credentials — use environment variables }, ``` -------------------------------- ### node-postgres (pg) - Pool Configuration Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Recommended for Node.js servers. Configure a connection pool for better performance and manage its lifecycle by closing it on shutdown. ```typescript import { drizzle } from 'drizzle-orm/node-postgres'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, }); const db = drizzle({ client: pool }); ``` ```typescript process.on('SIGTERM', async () => { await pool.end(); }); ``` -------------------------------- ### Configure Postgres-Specific Index Types Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Demonstrates how to define various Postgres-specific index types like GIN, GiST, BRIN, and partial indexes using Drizzle ORM. Choose the index type that best suits your data and query patterns. ```typescript (table) => [ // GIN — for JSONB, arrays, full-text search t.index('idx_gin').using('gin', table.metadata), // GiST — for geometric, range, and full-text data t.index('idx_gist').using('gist', table.location), // BRIN — for large, naturally ordered tables (timestamps, sequential IDs) t.index('idx_brin').using('brin', table.createdAt), // Partial index — index only rows matching a condition t.index('idx_active').on(table.email).where(sql`${table.isActive} = true`) ] ``` -------------------------------- ### Direct Schema Push Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md This command is suitable for local development and prototyping as it applies schema changes directly to the database without generating migration files. ```bash npx drizzle-kit push ``` -------------------------------- ### Using Placeholders for Parameters Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-prepared-statements.md Utilize `sql.placeholder()` for parameterized values in prepared statements. This allows multiple parameters to be bound to a single prepared query. ```typescript import { sql } from 'drizzle-orm'; const getPostsByAuthor = db .select() .from(posts) .where( and( eq(posts.authorId, sql.placeholder('authorId')), eq(posts.published, sql.placeholder('published')), ) ) .limit(sql.placeholder('limit')) .prepare('get_posts_by_author'); const result = await getPostsByAuthor.execute({ authorId: 1, published: true, limit: 10, }); ``` -------------------------------- ### Programmatically Apply Migrations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Execute migrations programmatically within your application code, typically used in deploy scripts or serverless functions. Ensure the import path for `migrate` matches your database driver. ```typescript import { drizzle } from 'drizzle-orm/node-postgres'; import { migrate } from 'drizzle-orm/node-postgres/migrator'; const db = drizzle(process.env.DATABASE_URL!); await migrate(db, { migrationsFolder: './drizzle' }); ``` ```typescript import { migrate } from 'drizzle-orm/node-postgres/migrator'; // or import { migrate } from 'drizzle-orm/postgres-js/migrator'; // or import { migrate } from 'drizzle-orm/neon-http/migrator'; // or import { migrate } from 'drizzle-orm/neon-serverless/migrator'; ``` -------------------------------- ### Neon HTTP - Serverless Client Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Ideal for serverless and edge runtimes. This driver uses HTTP for queries and does not require a connection pool. ```typescript import { neon } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-http'; const sql = neon(process.env.DATABASE_URL!); const db = drizzle({ client: sql }); ``` -------------------------------- ### Creating Partial Types with Pick and Omit Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/types-inference.md Demonstrates how to create more specific types from inferred select or insert types using TypeScript's built-in `Pick` and `Omit` utility types, useful for scenarios requiring only a subset of fields. ```typescript type UserProfile = Pick; type UserUpdate = Partial>; ``` -------------------------------- ### Legacy Relations Schema Initialization Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-defining.md When using the legacy relations API, the entire schema must be passed to the `drizzle()` constructor. ```typescript import * as schema from './schema'; const db = drizzle({ schema }); ``` -------------------------------- ### Using sql.raw() for Unparameterized SQL Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-sql-operator.md Employ `sql.raw()` for SQL fragments like table or column names that should not be parameterized. Exercise caution as this bypasses parameterization and introduces SQL injection risks if used with untrusted input. ```typescript const tableName = 'users'; const columnName = 'name'; // sql.raw() — no parameterization (be careful with user input!) sql`select * from ${sql.raw(tableName)} order by ${sql.raw(columnName)}`; // NEVER use sql.raw() with user-provided values — SQL injection risk ``` -------------------------------- ### Incorrect Configuration: Using require() Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Avoid using `require()` syntax in `drizzle.config.ts` as it is an ES module. Use `import` instead. ```typescript // Wrong: using require() syntax — drizzle.config.ts is an ES module const { defineConfig } = require('drizzle-kit'); ``` -------------------------------- ### Incorrect: Selecting all columns with select() Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-select-patterns.md Avoid fetching all columns when only a few are needed. This can lead to performance issues and unnecessary data transfer. ```typescript const users = await db.select().from(usersTable); ``` -------------------------------- ### Table-Level Foreign Key with onDelete Cascade Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md Demonstrates defining a foreign key constraint at the table level, including a specific `onDelete` behavior (cascade). ```typescript (table) => [ t.foreignKey({ columns: [table.authorId], foreignColumns: [users.id], }).onDelete('cascade'), ] ``` -------------------------------- ### Define and Query Postgres Views Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/engine-postgres.md Shows how to create and query PostgreSQL views using Drizzle ORM. Views can simplify complex queries and provide a consistent interface to your data. ```typescript import { pgView } from 'drizzle-orm/pg-core'; export const activeUsers = pgView('active_users').as((qb) => qb.select().from(users).where(eq(users.isActive, true)) ); // Query the view like a table await db.select().from(activeUsers); ``` -------------------------------- ### Cursor-based Pagination with Drizzle Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-dynamic-queries.md Implement performant cursor-based pagination, ideal for large datasets. Use a cursor value (e.g., the last seen ID) with `where` and `limit` to fetch subsequent records efficiently. Ensure `gt` is imported. ```typescript async function getUsers(cursor?: number, pageSize = 20) { const query = db .select() .from(users) .orderBy(users.id) .limit(pageSize); if (cursor) { return query.where(gt(users.id, cursor)); } return query; ``` -------------------------------- ### Correct Product Schema Definition with Drizzle ORM Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-column-types.md Illustrates the correct way to define a product schema using Drizzle ORM's PostgreSQL types, including auto-generated IDs, precise numeric types, native booleans, typed JSONB, timezone-aware timestamps, and native arrays for better data integrity and type safety. ```typescript import { pgTable, integer, numeric, boolean, jsonb, timestamp, text } from 'drizzle-orm/pg-core'; import * as t from 'drizzle-orm/pg-core'; export const products = pgTable('products', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), price: t.numeric('price', { precision: 10, scale: 2 }).notNull(), isActive: t.boolean('is_active').notNull().default(true), metadata: t.jsonb('metadata').$type<{ color: string; size: string }>(), createdAt: t.timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), tags: t.text('tags').array(), }); ``` -------------------------------- ### Module-Level Initialization for Warm Invocations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Initialize Drizzle clients outside of your handler function to reuse them across warm serverless invocations. This improves performance by avoiding repeated client creation. ```typescript import { neon } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-http'; // Created once, reused across warm invocations const sql = neon(process.env.DATABASE_URL!); const db = drizzle({ client: sql }); export async function handler(event: any) { const users = await db.select().from(usersTable); return { statusCode: 200, body: JSON.stringify(users) }; } ``` -------------------------------- ### Composite Index for Multi-Column Queries Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-indexes-constraints.md Creates a composite index on `userId` and `type` for the `events` table to optimize queries that filter by both columns simultaneously. ```typescript export const events = pgTable( 'events', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), userId: t.integer('user_id').notNull(), type: t.varchar('type', { length: 64 }).notNull(), createdAt: t.timestamp('created_at').notNull().defaultNow(), }, (table) => [ // Composite index for queries filtering by userId + type t.index('events_user_type_idx').on(table.userId, table.type), ] ); ``` -------------------------------- ### Neon WebSocket Driver for Serverless Transactions Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Use the Neon WebSocket driver when transactions are needed in serverless environments without persistent TCP connections. Configure the WebSocket constructor for the driver. ```typescript import { Pool, neonConfig } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-serverless'; import ws from 'ws'; neonConfig.webSocketConstructor = ws; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const db = drizzle({ client: pool }); ``` -------------------------------- ### Prepared Statements with Relational Queries Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-prepared-statements.md Prepared statements can be used with Drizzle's relational query API. This allows for efficient retrieval of related data when the same query structure is executed repeatedly. ```typescript const getUserWithPosts = db.query.users.findFirst({ where: eq(users.id, sql.placeholder('id')), with: { posts: true, }, }).prepare('get_user_with_posts'); const result = await getUserWithPosts.execute({ id: 1 }); ``` -------------------------------- ### Multiple Schema Files with Array Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-config.md Configures drizzle-kit to include multiple schema files by providing an explicit array of paths. Use this when you need to specify exact files. ```typescript schema: ['./src/db/schema/users.ts', './src/db/schema/posts.ts'], ``` -------------------------------- ### Inspecting Query SQL and Parameters Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-postgres.md Use the `.toSQL()` method on a Drizzle query object to inspect the generated SQL and its parameters without executing the query. ```typescript const query = db .select() .from(usersTable) .where(eq(usersTable.id, 1)) .toSQL(); console.log(query.sql); // SELECT ... FROM "users" WHERE "users"."id" = $1 console.log(query.params); // [1] ``` -------------------------------- ### Find a Single User with Posts Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-querying.md Use `findFirst` to retrieve a single record and its related data. The `where` clause filters the primary record. ```typescript const user = await db.query.users.findFirst({ where: eq(users.id, 1), with: { posts: true, }, }); // { id: 1, name: "Alice", posts: [...] } | undefined ``` -------------------------------- ### Dynamic Query Construction with Conditional Filters Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-filters-operators.md Build query conditions dynamically based on input filters by collecting conditions in an array and applying them using `and`. ```typescript const conditions = []; if (filters.role) { conditions.push(eq(users.role, filters.role)); } if (filters.isActive !== undefined) { conditions.push(eq(users.isActive, filters.isActive)); } const result = await db .select() .from(users) .where(conditions.length > 0 ? and(...conditions) : undefined); ``` -------------------------------- ### Dynamic SELECT (Partial Select) with Drizzle Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-dynamic-queries.md Construct SELECT clauses dynamically to include specific columns based on runtime conditions. This allows for partial data retrieval, improving performance by reducing data transfer. ```typescript async function getUsers(includeEmail: boolean) { const columns: Record = { id: users.id, name: users.name, }; if (includeEmail) { columns.email = users.email; } return db.select(columns).from(users); ``` -------------------------------- ### Using $dynamic() for Query Composition Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-dynamic-queries.md Utilize the `.$dynamic()` method to create a base query that can be further modified across function boundaries. This is useful for composing queries with reusable logic, such as pagination. ```typescript function withPagination( query: T, page: number, pageSize: number, ) { return query.limit(pageSize).offset((page - 1) * pageSize); } // Usage const baseQuery = db.select().from(users).$dynamic(); const paginatedQuery = withPagination(baseQuery, 1, 20); const results = await paginatedQuery; ``` -------------------------------- ### Define Relations v2 (Recommended) Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-defining.md Use `defineRelations` for new projects to describe table connections. Explicitly map `from`/`to` columns for `r.one` relations. `r.many` infers connections from the other side. ```typescript import { defineRelations } from 'drizzle-orm'; import * as schema from './schema'; export const relations = defineRelations(schema, (r) => ({ users: { posts: r.many.posts(), profile: r.one.profiles({ from: r.users.id, to: r.profiles.userId, }), }, posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id, }), comments: r.many.comments(), }, comments: { post: r.one.posts({ from: r.comments.postId, to: r.posts.id, }), author: r.one.users({ from: r.comments.authorId, to: r.users.id, }), }, profiles: { user: r.one.users({ from: r.profiles.userId, to: r.users.id, }), }, })); // Pass relations to drizzle() const db = drizzle({ client: pool, relations }); ``` -------------------------------- ### Incorrect: Client Creation Inside Handler Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/driver-serverless.md Avoid creating Drizzle clients inside your serverless function handler. This leads to unnecessary re-initialization on every invocation, negating performance benefits. ```typescript // Wrong: creating the client inside the handler export async function handler(event) { const sql = neon(process.env.DATABASE_URL!); // Re-created on every invocation const db = drizzle({ client: sql }); // Move this outside the handler } ``` -------------------------------- ### Incorrect Product Schema Definition Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-column-types.md Demonstrates common mistakes in defining a product schema, such as storing numbers as text, booleans as text, and JSON as plain text, leading to potential runtime issues and lack of database-level validation. ```typescript import { pgTable, text, integer, varchar } from 'drizzle-orm/pg-core'; export const products = pgTable('products', { id: integer('id').primaryKey(), // no auto-generation strategy price: text('price'), // storing numbers as text isActive: text('is_active'), // storing booleans as text metadata: text('metadata'), // storing JSON as plain text createdAt: text('created_at'), // storing timestamps as text tags: varchar('tags', { length: 1000 }), // storing arrays as comma-separated strings }); ``` -------------------------------- ### Inferring Select and Insert Types from Table Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/types-inference.md Demonstrates three methods to infer TypeScript types for database rows (select) and insert operations from a Drizzle schema table. Use the method that best suits your readability preferences. ```typescript import { pgTable, integer, text, boolean, timestamp } from 'drizzle-orm/pg-core'; const users = pgTable('users', { id: integer().primaryKey().generatedAlwaysAsIdentity(), name: text('name').notNull(), email: text('email').notNull(), isActive: boolean('is_active').notNull().default(true), createdAt: timestamp('created_at').notNull().defaultNow(), }); // Method 1: $inferSelect and $inferInsert on the table object type SelectUser = typeof users.$inferSelect; // { id: number; name: string; email: string; isActive: boolean; createdAt: Date } type InsertUser = typeof users.$inferInsert; // { name: string; email: string; id?: number; isActive?: boolean; createdAt?: Date } // Note: columns with defaults or generated values become optional in insert type // Method 2: Utility types from drizzle-orm import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm'; type SelectUser = InferSelectModel; type InsertUser = InferInsertModel; // Method 3: Via the _ namespace (equivalent to $inferSelect) type SelectUser = typeof users._.$inferSelect; type InsertUser = typeof users._.$inferInsert; ``` -------------------------------- ### SQL Aggregations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-sql-operator.md Perform common SQL aggregations like count, average, max, and min using the `sql` operator, ensuring correct typing with generics and runtime mapping. ```typescript const stats = await db.select({ count: sql`count(*)`.mapWith(Number), avgPrice: sql`avg(${products.price})`.mapWith(Number), maxPrice: sql`max(${products.price})`.mapWith(Number), minPrice: sql`min(${products.price})`.mapWith(Number), }).from(products); ``` -------------------------------- ### Select vs. Insert Type Differences Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/types-inference.md Illustrates the structural differences between select and insert types. Select types require all fields, representing a complete row, while insert types make fields optional if they have defaults, auto-increment, or generated values. ```typescript type SelectUser = { id: number; // required — always present in a SELECT result name: string; // required email: string; // required isActive: boolean; // required createdAt: Date; // required }; type InsertUser = { id?: number; // optional — generated by identity name: string; // required — no default email: string; // required — no default isActive?: boolean; // optional — has .default(true) createdAt?: Date; // optional — has .defaultNow() }; ``` -------------------------------- ### Execute Multiple Statements with db.batch() Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-batch-operations.md Use `db.batch()` to execute a list of statements in a single request. The response is a typed tuple matching the order of the statements. If any statement fails, the entire batch is rolled back. ```typescript const batchResponse = await db.batch([ db.insert(users).values({ id: 1, name: 'Alice' }).returning({ id: users.id }), db.update(users).set({ name: 'Alice Updated' }).where(eq(users.id, 1)), db.query.users.findMany({}), db.select().from(users).where(eq(users.id, 1)), ]); ``` -------------------------------- ### Incorrect: Using SQL-like API for simple relational fetches Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-select-patterns.md The SQL-like API returns flat objects for joins, not nested structures. Use the relational query API for nested data to avoid manual reshaping and null checks. ```typescript const postsWithAuthors = await db .select() .from(postsTable) .leftJoin(usersTable, eq(postsTable.authorId, usersTable.id)); ``` -------------------------------- ### Chunking Large Inserts Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-batch-operations.md For inserting thousands of rows, consider chunking the data into smaller batches to avoid overwhelming the database or exceeding request limits. This loop processes the data in chunks of `CHUNK_SIZE`. ```typescript const CHUNK_SIZE = 1000; const allUsers = [...]; // thousands of rows for (let i = 0; i < allUsers.length; i += CHUNK_SIZE) { const chunk = allUsers.slice(i, i + CHUNK_SIZE); await db.insert(users).values(chunk); } ``` -------------------------------- ### Dynamic Relational Queries with Drizzle Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-dynamic-queries.md Dynamically include related data in queries using the `with` clause. This allows conditional loading of relations based on query options. Note that dynamic `with` clauses may reduce type safety for returned relations. ```typescript interface QueryOptions { includePosts?: boolean; includeProfile?: boolean; } async function getUser(id: number, options: QueryOptions) { const withClause: Record = {}; if (options.includePosts) { withClause.posts = true; } if (options.includeProfile) { withClause.profile = true; } return db.query.users.findFirst({ where: eq(users.id, id), with: withClause, }); ``` -------------------------------- ### Basic Transaction Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-mutations.md Wrap multiple mutations within a transaction to ensure atomicity. If any mutation fails, all changes within the transaction are rolled back. ```typescript await db.transaction(async (tx) => { const [user] = await tx .insert(usersTable) .values({ name: 'Alice' }) .returning(); await tx.insert(profilesTable).values({ userId: user.id, bio: 'Hello world', }); }); // If either insert fails, both are rolled back ``` -------------------------------- ### Count with Conditions Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-select-patterns.md You can count records that meet specific conditions by using the `filter` clause within the `sql` tag for count queries. ```typescript const [{ activeCount }] = await db .select({ activeCount: sql`count(*) filter (where ${usersTable.isActive})`.mapWith(Number), }) .from(usersTable); ``` -------------------------------- ### Correct: Partial Select - Fetch only needed columns Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-select-patterns.md Use partial selects to specify exactly which columns to fetch, improving performance and reducing data transfer. The type is inferred from the selected fields. ```typescript const userEmails = await db .select({ id: usersTable.id, email: usersTable.email, }) .from(usersTable); ``` -------------------------------- ### Correct: Joins - SQL-level control with partial select Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-select-patterns.md For complex multi-table joins requiring SQL-level control, use the `select()` builder with explicit `innerJoin` and specify the desired columns for the result. ```typescript const result = await db .select({ postTitle: postsTable.title, authorName: usersTable.name, }) .from(postsTable) .innerJoin(usersTable, eq(postsTable.authorId, usersTable.id)); ``` -------------------------------- ### Pull Schema from Database Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Use this command to read your database schema and generate Drizzle schema TypeScript files. It's useful for adopting Drizzle in existing projects or migrating from other ORMs. ```bash npx drizzle-kit pull ``` -------------------------------- ### Correct Post Table Definition with Foreign Key Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-table-definitions.md Define post tables with consistent naming, proper data types, and foreign key constraints to enforce referential integrity. Use `generatedAlwaysAsIdentity()` for primary keys and `defaultNow()` for timestamps. ```typescript import { pgTable, varchar, integer, text, timestamp } from 'drizzle-orm/pg-core'; import * as t from 'drizzle-orm/pg-core'; import { users } from './users'; export const posts = pgTable('posts', { id: t.integer().primaryKey().generatedAlwaysAsIdentity(), title: t.varchar('title', { length: 256 }).notNull(), content: t.text('content'), authorId: t.integer('author_id').notNull().references(() => users.id), createdAt: t.timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }); ``` -------------------------------- ### Correct Way to Aggregate Data Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/relations-querying.md Avoid using the relational API for aggregations like COUNT or SUM. Instead, use `db.select()` with SQL functions for efficient aggregation. ```typescript // Better: const [{ count }] = await db .select({ count: sql`count(*)`.mapWith(Number) }) .from(users); ``` -------------------------------- ### Importing Driver-Specific Error Classes Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/query-error-handling.md Import the correct error class based on the database driver you are using. This ensures proper error type checking. ```typescript // node-postgres import { DatabaseError } from 'pg'; ``` ```typescript // postgres.js — errors have the same code property but different class import postgres from 'postgres'; // postgres.js throws PostgresError instances // Access error.code the same way ``` ```typescript // Neon — wraps errors similarly to node-postgres import { neon } from '@neondatabase/serverless'; ``` -------------------------------- ### Incorrect Pattern: Not Committing Migrations Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/migrations-workflow.md Ensure that migration files are committed to version control. This is crucial for keeping all team members and CI/CD pipelines synchronized. ```typescript // Wrong: not committing migration files to version control // Migration files should be committed so all team members and CI/CD stay in sync ``` -------------------------------- ### Barrel Export for Schema Index Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/schema-table-definitions.md Use a barrel export file (`index.ts`) to consolidate all table definitions from separate schema files. This provides a single import point for your entire schema. ```typescript // db/schema/index.ts - barrel export export * from './users'; export * from './posts'; ``` -------------------------------- ### PostgreSQL Statement Naming Convention Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/perf-prepared-statements.md For PostgreSQL, a unique statement name is mandatory when using `.prepare()`. Choose descriptive names that clearly indicate the query's purpose. ```typescript .prepare('statement_name'); ``` -------------------------------- ### SQL GROUP BY Clause Source: https://github.com/honra-io/drizzle-best-practices/blob/main/references/advanced-sql-operator.md Implement `GROUP BY` clauses in your Drizzle queries using the `sql` operator for aggregate functions, such as counting posts per author. ```typescript const postCountByAuthor = await db.select({ authorId: posts.authorId, postCount: sql`count(*)`.mapWith(Number), }).from(posts) .groupBy(posts.authorId); ```