drizzle

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Drizzle Core Knowledge

Drizzle核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
drizzle
for comprehensive documentation.
深度知识:如需完整文档,请调用
mcp__documentation__fetch_docs
并指定技术为
drizzle

When NOT to Use This Skill

本技能不适用场景

  • Existing Prisma Projects: Use
    prisma
    skill for Prisma-based codebases
  • TypeORM Projects: Use
    typeorm
    skill for TypeORM-based applications
  • Raw SQL Execution: Use
    database-query
    MCP server for direct SQL queries
  • NoSQL Databases: Use
    mongodb
    skill for MongoDB operations
  • Complex ORM Features: Drizzle is lightweight; consider Prisma/TypeORM for advanced features
  • Database Architecture: Consult
    sql-expert
    or
    architect-expert
    for schema design
  • 现有Prisma项目:基于Prisma的代码库请使用
    prisma
    技能
  • TypeORM项目:基于TypeORM的应用请使用
    typeorm
    技能
  • 原生SQL执行:直接运行SQL查询请使用
    database-query
    MCP服务
  • NoSQL数据库:MongoDB操作请使用
    mongodb
    技能
  • 复杂ORM特性需求:Drizzle是轻量级ORM,如需高级特性请考虑Prisma/TypeORM
  • 数据库架构设计:模式设计请咨询
    sql-expert
    architect-expert

Schema Definition

模式定义

typescript
// schema.ts
import { pgTable, serial, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).unique().notNull(),
  name: varchar('name', { length: 100 }),
  createdAt: timestamp('created_at').defaultNow(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: varchar('content'),
  published: boolean('published').default(false),
  authorId: integer('author_id').references(() => users.id),
});
typescript
// schema.ts
import { pgTable, serial, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).unique().notNull(),
  name: varchar('name', { length: 100 }),
  createdAt: timestamp('created_at').defaultNow(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: varchar('content'),
  published: boolean('published').default(false),
  authorId: integer('author_id').references(() => users.id),
});

CRUD Operations

CRUD操作

typescript
import { eq, and, like, desc } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';

// Create
const [user] = await db.insert(users)
  .values({ email: 'user@example.com', name: 'John' })
  .returning();

// Read
const allUsers = await db.select().from(users)
  .where(like(users.email, '%@example.com'))
  .orderBy(desc(users.createdAt))
  .limit(10);

const user = await db.select().from(users)
  .where(eq(users.id, 1))
  .limit(1);

// Update
await db.update(users)
  .set({ name: 'Jane' })
  .where(eq(users.id, 1));

// Delete
await db.delete(users).where(eq(users.id, 1));
typescript
import { eq, and, like, desc } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';

// Create
const [user] = await db.insert(users)
  .values({ email: 'user@example.com', name: 'John' })
  .returning();

// Read
const allUsers = await db.select().from(users)
  .where(like(users.email, '%@example.com'))
  .orderBy(desc(users.createdAt))
  .limit(10);

const user = await db.select().from(users)
  .where(eq(users.id, 1))
  .limit(1);

// Update
await db.update(users)
  .set({ name: 'Jane' })
  .where(eq(users.id, 1));

// Delete
await db.delete(users).where(eq(users.id, 1));

Joins

联表查询

typescript
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postTitle: posts.title,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .where(eq(posts.published, true));
typescript
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postTitle: posts.title,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .where(eq(posts.published, true));

Migrations

数据迁移

bash
npx drizzle-kit generate
npx drizzle-kit migrate
npx drizzle-kit studio
bash
npx drizzle-kit generate
npx drizzle-kit migrate
npx drizzle-kit studio

Anti-Patterns

反模式

Anti-PatternWhy It's BadBetter Approach
Not using connection poolingConnection exhaustion, poor performanceUse
pg.Pool
or equivalent with proper limits
Selecting all columns with
select()
Unnecessary data transferSpecify only needed columns in select object
Manual SQL string concatenationSQL injection risk, type unsafetyUse Drizzle query builder with parameterization
No transaction for related operationsData inconsistencyUse
db.transaction()
for atomic operations
Missing indexes on filter columnsSlow queriesAdd
.index()
to frequently queried columns
Not reusing prepared statementsSlower execution, resource wasteUse
.prepare()
for repeated queries
Hardcoded connection stringsSecurity riskUse environment variables
No error handling on queriesPoor UX, silent failuresWrap queries in try-catch
Using
drizzle-kit push
in production
No migration history, riskyUse
generate
+
migrate
workflow
Not defining foreign key constraintsData integrity issuesUse
.references()
in schema
反模式弊端优化方案
不使用连接池连接耗尽、性能不佳使用带合理限制的
pg.Pool
或等效连接池
使用
select()
查询全部列
不必要的数据传输在select对象中仅指定需要的列
手动拼接SQL字符串存在SQL注入风险、无类型安全保障使用Drizzle查询构建器的参数化查询能力
关联操作不使用事务数据不一致使用
db.transaction()
实现原子操作
过滤列未加索引查询速度慢给高频查询的列添加
.index()
不复用预处理语句执行速度慢、资源浪费重复执行的查询使用
.prepare()
硬编码连接字符串存在安全风险使用环境变量存储敏感信息
查询未做错误处理用户体验差、错误静默失败用try-catch包裹查询逻辑
生产环境使用
drizzle-kit push
无迁移历史、风险高使用
generate
+
migrate
工作流
未定义外键约束数据完整性问题在模式中使用
.references()
定义关联

Quick Troubleshooting

快速排障

IssueLikely CauseSolution
"relation does not exist"Schema not migrated or wrong DBRun
drizzle-kit migrate
, check connection
"column does not exist"Schema out of sync with codeRegenerate and apply migrations
Type errors on queriesSchema types not matching DBRun
drizzle-kit generate
to sync types
Slow queriesMissing indexes, N+1 queriesAdd indexes, use joins instead of separate queries
Connection timeoutsPool exhausted or network issuesCheck pool size, increase timeout limits
"Cannot find module 'drizzle-orm'"Missing dependencyRun
npm install drizzle-orm
Migration conflictsMultiple devs generating migrationsCoordinate migration naming, merge carefully
"ECONNREFUSED"Database not running or wrong URLVerify DATABASE_URL, start database
Foreign key violationsInserting with invalid referencesEnsure referenced records exist first
Duplicate key errorsUnique constraint violationCheck for existing record before insert
问题可能原因解决方案
"relation does not exist"模式未迁移或连接了错误的数据库运行
drizzle-kit migrate
,检查连接配置
"column does not exist"模式与代码不同步重新生成并应用迁移
查询出现类型错误模式类型与数据库不匹配运行
drizzle-kit generate
同步类型
查询速度慢缺少索引、存在N+1查询添加索引,用联表代替多次独立查询
连接超时连接池耗尽或网络问题检查连接池大小,调高超时限制
"Cannot find module 'drizzle-orm'"缺少依赖运行
npm install drizzle-orm
迁移冲突多个开发者同时生成迁移统一迁移命名规范,谨慎合并迁移文件
"ECONNREFUSED"数据库未启动或URL错误校验DATABASE_URL,启动数据库服务
外键违规插入了无效的关联引用先确保被关联的记录已存在
重复键错误违反唯一约束插入前检查是否已存在对应记录

Production Readiness

生产就绪指南

Database Connection

数据库连接

typescript
// db.ts
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,
  // SECURITY: Use proper CA certificate in production instead of disabling verification
  // ssl: { rejectUnauthorized: false } is INSECURE - vulnerable to MITM attacks
  ssl: process.env.NODE_ENV === 'production'
    ? { ca: process.env.DB_CA_CERT }
    : false,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 10000,
});

pool.on('error', (err) => {
  console.error('Unexpected pool error:', err);
});

export const db = drizzle(pool, { schema });

// Health check
export async function healthCheck() {
  const client = await pool.connect();
  try {
    await client.query('SELECT 1');
    return { status: 'healthy' };
  } finally {
    client.release();
  }
}

// Graceful shutdown
export async function closePool() {
  await pool.end();
}
typescript
// db.ts
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,
  // SECURITY: Use proper CA certificate in production instead of disabling verification
  // ssl: { rejectUnauthorized: false } is INSECURE - vulnerable to MITM attacks
  ssl: process.env.NODE_ENV === 'production'
    ? { ca: process.env.DB_CA_CERT }
    : false,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 10000,
});

pool.on('error', (err) => {
  console.error('Unexpected pool error:', err);
});

export const db = drizzle(pool, { schema });

// Health check
export async function healthCheck() {
  const client = await pool.connect();
  try {
    await client.query('SELECT 1');
    return { status: 'healthy' };
  } finally {
    client.release();
  }
}

// Graceful shutdown
export async function closePool() {
  await pool.end();
}

Transaction Handling

事务处理

typescript
import { db } from './db';

async function transferFunds(fromId: string, toId: string, amount: number) {
  return await db.transaction(async (tx) => {
    const [from] = await tx
      .select()
      .from(accounts)
      .where(eq(accounts.id, fromId))
      .for('update');

    if (!from || from.balance < amount) {
      throw new Error('Insufficient funds');
    }

    await tx
      .update(accounts)
      .set({ balance: sql`${accounts.balance} - ${amount}` })
      .where(eq(accounts.id, fromId));

    await tx
      .update(accounts)
      .set({ balance: sql`${accounts.balance} + ${amount}` })
      .where(eq(accounts.id, toId));

    return { success: true };
  });
}
typescript
import { db } from './db';

async function transferFunds(fromId: string, toId: string, amount: number) {
  return await db.transaction(async (tx) => {
    const [from] = await tx
      .select()
      .from(accounts)
      .where(eq(accounts.id, fromId))
      .for('update');

    if (!from || from.balance < amount) {
      throw new Error('Insufficient funds');
    }

    await tx
      .update(accounts)
      .set({ balance: sql`${accounts.balance} - ${amount}` })
      .where(eq(accounts.id, fromId));

    await tx
      .update(accounts)
      .set({ balance: sql`${accounts.balance} + ${amount}` })
      .where(eq(accounts.id, toId));

    return { success: true };
  });
}

Query Optimization

查询优化

typescript
// Pagination with cursor
async function getUsers(cursor?: string, limit = 20) {
  const query = db.select().from(users);

  if (cursor) {
    query.where(gt(users.id, cursor));
  }

  const results = await query
    .orderBy(asc(users.id))
    .limit(limit + 1);

  const hasMore = results.length > limit;
  const data = hasMore ? results.slice(0, -1) : results;

  return {
    data,
    nextCursor: hasMore ? data[data.length - 1].id : null,
  };
}

// Batch inserts
async function bulkInsertUsers(usersData: NewUser[]) {
  const batchSize = 100;
  for (let i = 0; i < usersData.length; i += batchSize) {
    const batch = usersData.slice(i, i + batchSize);
    await db.insert(users).values(batch);
  }
}

// Select only needed columns
const userNames = await db
  .select({ id: users.id, name: users.name })
  .from(users)
  .where(eq(users.isActive, true));
typescript
// Pagination with cursor
async function getUsers(cursor?: string, limit = 20) {
  const query = db.select().from(users);

  if (cursor) {
    query.where(gt(users.id, cursor));
  }

  const results = await query
    .orderBy(asc(users.id))
    .limit(limit + 1);

  const hasMore = results.length > limit;
  const data = hasMore ? results.slice(0, -1) : results;

  return {
    data,
    nextCursor: hasMore ? data[data.length - 1].id : null,
  };
}

// Batch inserts
async function bulkInsertUsers(usersData: NewUser[]) {
  const batchSize = 100;
  for (let i = 0; i < usersData.length; i += batchSize) {
    const batch = usersData.slice(i, i + batchSize);
    await db.insert(users).values(batch);
  }
}

// Select only needed columns
const userNames = await db
  .select({ id: users.id, name: users.name })
  .from(users)
  .where(eq(users.isActive, true));

Migration Strategy

迁移策略

typescript
// drizzle.config.ts
import type { Config } from 'drizzle-kit';

export default {
  schema: './src/db/schema.ts',
  out: './migrations',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
  strict: true,
  verbose: true,
} satisfies Config;

// package.json scripts
// "db:generate": "drizzle-kit generate:pg",
// "db:migrate": "drizzle-kit migrate",
// "db:push": "drizzle-kit push:pg", // Dev only
// "db:studio": "drizzle-kit studio"
typescript
// drizzle.config.ts
import type { Config } from 'drizzle-kit';

export default {
  schema: './src/db/schema.ts',
  out: './migrations',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
  strict: true,
  verbose: true,
} satisfies Config;

// package.json scripts
// "db:generate": "drizzle-kit generate:pg",
// "db:migrate": "drizzle-kit migrate",
// "db:push": "drizzle-kit push:pg", // Dev only
// "db:studio": "drizzle-kit studio"

Type-Safe Prepared Statements

类型安全预处理语句

typescript
import { sql } from 'drizzle-orm';

const getUserById = db.query.users
  .findFirst({
    where: eq(users.id, sql.placeholder('id')),
    with: { posts: true },
  })
  .prepare('get_user_by_id');

// Usage (reuses execution plan)
const user = await getUserById.execute({ id: userId });
typescript
import { sql } from 'drizzle-orm';

const getUserById = db.query.users
  .findFirst({
    where: eq(users.id, sql.placeholder('id')),
    with: { posts: true },
  })
  .prepare('get_user_by_id');

// Usage (reuses execution plan)
const user = await getUserById.execute({ id: userId });

Testing

测试

typescript
// tests/db.test.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';

describe('Database', () => {
  let pool: Pool;
  let testDb: ReturnType<typeof drizzle>;

  beforeAll(async () => {
    pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
    testDb = drizzle(pool);
    await migrate(testDb, { migrationsFolder: './migrations' });
  });

  afterAll(async () => {
    await pool.end();
  });

  beforeEach(async () => {
    await testDb.delete(users);
  });

  it('should create user', async () => {
    const [user] = await testDb
      .insert(users)
      .values({ email: 'test@example.com', name: 'Test' })
      .returning();

    expect(user.email).toBe('test@example.com');
  });
});
typescript
// tests/db.test.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';

describe('Database', () => {
  let pool: Pool;
  let testDb: ReturnType<typeof drizzle>;

  beforeAll(async () => {
    pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
    testDb = drizzle(pool);
    await migrate(testDb, { migrationsFolder: './migrations' });
  });

  afterAll(async () => {
    await pool.end();
  });

  beforeEach(async () => {
    await testDb.delete(users);
  });

  it('should create user', async () => {
    const [user] = await testDb
      .insert(users)
      .values({ email: 'test@example.com', name: 'Test' })
      .returning();

    expect(user.email).toBe('test@example.com');
  });
});

Monitoring Metrics

监控指标

MetricTarget
Query time (p99)< 100ms
Connection pool usage< 80%
Migration success100%
Transaction rollbacks< 0.1%
指标目标值
查询耗时(p99)< 100ms
连接池使用率< 80%
迁移成功率100%
事务回滚率< 0.1%

Checklist

检查清单

  • Connection pooling configured
  • SSL in production
  • Transactions for multi-step operations
  • Cursor-based pagination
  • Batch operations for bulk data
  • Prepared statements for repeated queries
  • Migration versioning
  • Test database isolation
  • Query logging in development
  • Health check endpoint
  • 已配置连接池
  • 生产环境已启用SSL
  • 多步操作已使用事务
  • 已实现游标分页
  • 批量数据操作已使用批处理
  • 重复查询已使用预处理语句
  • 已做迁移版本管理
  • 测试数据库已隔离
  • 开发环境已开启查询日志
  • 已实现健康检查接口

Reference Documentation

参考文档

  • Schema
  • Queries
  • Schema
  • Queries