drizzle orm

Expert assistance with Drizzle ORM - TypeScript ORM for SQL databases.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "drizzle orm" with this command: npx skills add oriolrius/pki-manager-web/oriolrius-pki-manager-web-drizzle-orm

Drizzle ORM

Expert assistance with Drizzle ORM - TypeScript ORM for SQL databases.

Overview

Drizzle ORM is a lightweight TypeScript ORM:

  • Type-Safe: Full TypeScript type inference

  • SQL-Like: Familiar SQL syntax, not a new query language

  • Performant: Zero overhead, generates efficient SQL

  • Multiple Databases: PostgreSQL, MySQL, SQLite support

  • Migrations: Built-in migration system

  • Drizzle Studio: Visual database browser

Installation

Core packages

npm install drizzle-orm npm install --save-dev drizzle-kit

Database driver (choose one)

npm install better-sqlite3 # For SQLite npm install @types/better-sqlite3 --save-dev

Or for PostgreSQL

npm install postgres # For PostgreSQL npm install pg # Alternative PostgreSQL driver

Quick Start (SQLite)

  1. Define Schema

// src/db/schema.ts import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), });

export const posts = sqliteTable('posts', { id: text('id').primaryKey(), title: text('title').notNull(), content: text('content').notNull(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), });

  1. Create Database Client

// src/db/client.ts 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 });

  1. Use in Application

import { db } from './db/client'; import { users, posts } from './db/schema'; import { eq } from 'drizzle-orm';

// Insert const newUser = await db.insert(users).values({ id: '1', name: 'John Doe', email: 'john@example.com', }).returning();

// Query const allUsers = await db.select().from(users); const user = await db.select().from(users).where(eq(users.id, '1'));

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

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

Schema Definition

Column Types (SQLite)

import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core';

export const examples = sqliteTable('examples', { // Text id: text('id').primaryKey(), name: text('name').notNull(), description: text('description'),

// Integer age: integer('age'), count: integer('count').default(0),

// Boolean (stored as integer 0/1) isActive: integer('is_active', { mode: 'boolean' }).default(true),

// Timestamp (stored as integer unix epoch) createdAt: integer('created_at', { mode: 'timestamp' }), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }), // milliseconds

// Real (floating point) price: real('price'),

// Blob (binary data) data: blob('data', { mode: 'buffer' }),

// JSON (stored as text) metadata: text('metadata', { mode: 'json' }).$type<{ key: string; value: number }>(), });

Constraints

import { sqliteTable, text, integer, primaryKey, unique index } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', { id: text('id').primaryKey(), email: text('email').notNull().unique(), // Unique constraint name: text('name').notNull(), // Not null age: integer('age').default(18), // Default value }, (table) => ({ // Composite unique constraint emailNameUnique: unique().on(table.email, table.name), // Index emailIdx: index('email_idx').on(table.email), // Composite index nameAgeIdx: index('name_age_idx').on(table.name, table.age), }));

// Composite primary key export const userRoles = sqliteTable('user_roles', { userId: text('user_id').notNull(), roleId: text('role_id').notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.userId, table.roleId] }), }));

Check Constraints

import { sql } from 'drizzle-orm'; import { sqliteTable, text, integer, check } from 'drizzle-orm/sqlite-core';

export const certificates = sqliteTable('certificates', { id: text('id').primaryKey(), status: text('status').notNull(), serialNumber: text('serial_number').notNull(), }, (table) => ({ // Check constraint statusCheck: check('status_check', sql${table.status} IN ('active', 'revoked', 'expired')), }));

Foreign Keys

export const posts = sqliteTable('posts', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade', // Delete posts when user is deleted onUpdate: 'cascade', // Update posts when user id changes }), title: text('title').notNull(), });

// Self-referencing foreign key export const categories = sqliteTable('categories', { id: text('id').primaryKey(), name: text('name').notNull(), parentId: text('parent_id').references((): AnyPgColumn => categories.id), });

Default Values

import { sql } from 'drizzle-orm';

export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(),

// SQL default createdAt: integer('created_at').default(sql(unixepoch())),

// TypeScript default function id: text('id').$defaultFn(() => crypto.randomUUID()), createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()), });

Queries

Select

import { eq, and, or, gt, gte, lt, lte, like, inArray } from 'drizzle-orm';

// Select all columns const allUsers = await db.select().from(users);

// Select specific columns const names = await db.select({ id: users.id, name: users.name, }).from(users);

// Where clauses const user = await db.select().from(users).where(eq(users.id, '1'));

// Multiple conditions const activeAdults = await db.select().from(users).where( and( eq(users.isActive, true), gte(users.age, 18) ) );

// Or conditions const results = await db.select().from(users).where( or( eq(users.role, 'admin'), eq(users.role, 'moderator') ) );

// Like operator const johns = await db.select().from(users).where(like(users.name, '%John%'));

// In array const specificUsers = await db.select().from(users).where( inArray(users.id, ['1', '2', '3']) );

// Comparison operators const adults = await db.select().from(users).where(gte(users.age, 18)); const minors = await db.select().from(users).where(lt(users.age, 18));

// Order by const sorted = await db.select().from(users).orderBy(users.name); const descending = await db.select().from(users).orderBy(desc(users.createdAt));

// Limit and offset const paginated = await db.select().from(users).limit(10).offset(20);

// Get single result const user = await db.select().from(users).where(eq(users.id, '1')).get();

Joins

import { eq } from 'drizzle-orm';

// Inner join const usersWithPosts = await db .select() .from(users) .innerJoin(posts, eq(posts.userId, users.id));

// Left join const allUsersWithPosts = await db .select() .from(users) .leftJoin(posts, eq(posts.userId, users.id));

// Select specific columns from joined tables const results = await db .select({ userId: users.id, userName: users.name, postTitle: posts.title, }) .from(users) .leftJoin(posts, eq(posts.userId, users.id));

// Multiple joins const data = await db .select() .from(posts) .innerJoin(users, eq(posts.userId, users.id)) .leftJoin(comments, eq(comments.postId, posts.id));

Aggregations

import { count, sum, avg, min, max } from 'drizzle-orm';

// Count const userCount = await db.select({ count: count() }).from(users);

// Count with condition const activeCount = await db .select({ count: count() }) .from(users) .where(eq(users.isActive, true));

// Group by const postsByUser = await db .select({ userId: posts.userId, postCount: count(), }) .from(posts) .groupBy(posts.userId);

// Multiple aggregations const stats = await db .select({ total: count(), avgAge: avg(users.age), minAge: min(users.age), maxAge: max(users.age), }) .from(users);

// Having clause const activeUsers = await db .select({ userId: posts.userId, postCount: count(), }) .from(posts) .groupBy(posts.userId) .having(({ postCount }) => gt(postCount, 5));

Subqueries

import { sql } from 'drizzle-orm';

// Subquery in WHERE const sq = db.select({ userId: posts.userId }).from(posts).groupBy(posts.userId);

const activePosters = await db .select() .from(users) .where(inArray(users.id, sq));

// Subquery as column const usersWithPostCount = await db .select({ id: users.id, name: users.name, postCount: sql<number>( SELECT COUNT(*) FROM ${posts} WHERE ${posts.userId} = ${users.id} ), }) .from(users);

Insert

Single Insert

// Insert one await db.insert(users).values({ id: '1', name: 'John', email: 'john@example.com', });

// Insert with returning const newUser = await db.insert(users) .values({ id: '2', name: 'Jane', email: 'jane@example.com', }) .returning();

// Return specific columns const user = await db.insert(users) .values({ id: '3', name: 'Bob', email: 'bob@example.com' }) .returning({ id: users.id, name: users.name });

Bulk Insert

// Insert multiple await db.insert(users).values([ { id: '1', name: 'John', email: 'john@example.com' }, { id: '2', name: 'Jane', email: 'jane@example.com' }, { id: '3', name: 'Bob', email: 'bob@example.com' }, ]);

// Bulk insert with returning const newUsers = await db.insert(users) .values([ { id: '4', name: 'Alice', email: 'alice@example.com' }, { id: '5', name: 'Charlie', email: 'charlie@example.com' }, ]) .returning();

Upsert (Insert or Update)

// SQLite 3.24+ (ON CONFLICT) await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoUpdate({ target: users.id, set: { name: 'John Updated', email: 'john.updated@example.com' }, });

// Do nothing on conflict await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoNothing();

// Update specific columns await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoUpdate({ target: users.id, set: { updatedAt: sqlCURRENT_TIMESTAMP }, });

Update

import { eq } from 'drizzle-orm';

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

// Update multiple columns await db.update(users) .set({ name: 'Jane Smith', email: 'jane.smith@example.com', }) .where(eq(users.id, '2'));

// Update with returning const updated = await db.update(users) .set({ name: 'Bob Updated' }) .where(eq(users.id, '3')) .returning();

// Update with SQL expression await db.update(users) .set({ age: sql${users.age} + 1 }) .where(eq(users.id, '1'));

// Conditional update await db.update(users) .set({ status: 'active' }) .where(and( eq(users.verified, true), gte(users.createdAt, new Date('2024-01-01')) ));

Delete

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

// Delete multiple rows await db.delete(users).where(inArray(users.id, ['1', '2', '3']));

// Delete with condition await db.delete(users).where(lt(users.createdAt, new Date('2023-01-01')));

// Delete with returning const deleted = await db.delete(users) .where(eq(users.id, '1')) .returning();

// Delete all (be careful!) await db.delete(users);

Transactions

// Simple transaction await db.transaction(async (tx) => { await tx.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' }); await tx.insert(posts).values({ id: '1', title: 'First Post', userId: '1' }); });

// Transaction with rollback try { await db.transaction(async (tx) => { await tx.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' });

// This will cause transaction to rollback
throw new Error('Rollback!');

await tx.insert(posts).values({ id: '1', title: 'Post', userId: '1' });

}); } catch (error) { console.error('Transaction failed:', error); }

// Nested transactions await db.transaction(async (tx1) => { await tx1.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' });

await tx1.transaction(async (tx2) => { await tx2.insert(posts).values({ id: '1', title: 'Post', userId: '1' }); }); });

Relations

Define Relations

// src/db/schema.ts import { relations } from 'drizzle-orm'; import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), });

export const posts = sqliteTable('posts', { id: text('id').primaryKey(), title: text('title').notNull(), userId: text('user_id').notNull().references(() => users.id), });

export const comments = sqliteTable('comments', { id: text('id').primaryKey(), content: text('content').notNull(), postId: text('post_id').notNull().references(() => posts.id), userId: text('user_id').notNull().references(() => users.id), });

// Define relations export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts), comments: many(comments), }));

export const postsRelations = relations(posts, ({ one, many }) => ({ author: one(users, { fields: [posts.userId], references: [users.id], }), comments: many(comments), }));

export const commentsRelations = relations(comments, ({ one }) => ({ post: one(posts, { fields: [comments.postId], references: [posts.id], }), author: one(users, { fields: [comments.userId], references: [users.id], }), }));

Query with Relations

// Query with relations const usersWithPosts = await db.query.users.findMany({ with: { posts: true, }, });

// Nested relations const usersWithPostsAndComments = await db.query.users.findMany({ with: { posts: { with: { comments: true, }, }, }, });

// Filter relations const usersWithRecentPosts = await db.query.users.findMany({ with: { posts: { where: (posts, { gte }) => gte(posts.createdAt, new Date('2024-01-01')), }, }, });

// Select specific columns const data = await db.query.users.findMany({ columns: { id: true, name: true, }, with: { posts: { columns: { id: true, title: true, }, }, }, });

Migrations

Configuration

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

export default { schema: './src/db/schema.ts', out: './drizzle/migrations', driver: 'better-sqlite', dbCredentials: { url: './sqlite.db', }, } satisfies Config;

Generate Migrations

Generate migration from schema changes

npx drizzle-kit generate:sqlite

Custom migration name

npx drizzle-kit generate:sqlite --name add_users_table

Generate with custom config

npx drizzle-kit generate:sqlite --config drizzle.config.ts

Run Migrations

// src/db/migrate.ts import { drizzle } from 'drizzle-orm/better-sqlite3'; import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; import Database from 'better-sqlite3';

const sqlite = new Database('sqlite.db'); const db = drizzle(sqlite);

// Run migrations await migrate(db, { migrationsFolder: './drizzle/migrations' });

console.log('Migrations complete!'); sqlite.close();

Migration Files

-- drizzle/migrations/0001_add_users.sql CREATE TABLE users ( id text PRIMARY KEY NOT NULL, name text NOT NULL, email text NOT NULL UNIQUE, created_at integer NOT NULL );

CREATE INDEX email_idx ON users (email);

Drizzle Studio

Start Drizzle Studio

npx drizzle-kit studio

Custom port

npx drizzle-kit studio --port 3333

With custom config

npx drizzle-kit studio --config drizzle.config.ts

Access at: http://localhost:4983

TypeScript Integration

Infer Types

import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; import { users, posts } from './schema';

// Infer select model (what you get from queries) export type User = InferSelectModel<typeof users>; export type Post = InferSelectModel<typeof posts>;

// Infer insert model (what you need to insert) export type InsertUser = InferInsertModel<typeof users>; export type InsertPost = InferInsertModel<typeof posts>;

// Usage function createUser(user: InsertUser): Promise<User> { return db.insert(users).values(user).returning().get(); }

Typed Queries

// Type-safe query builder const query = db .select({ id: users.id, name: users.name, postCount: count(posts.id), }) .from(users) .leftJoin(posts, eq(posts.userId, users.id)) .groupBy(users.id);

// Infer result type type QueryResult = Awaited<ReturnType<typeof query.execute>>;

Best Practices

  • Use Transactions: Wrap multiple operations in transactions

  • Define Relations: Use relations for easier queries

  • Type Safety: Leverage TypeScript type inference

  • Migrations: Use migration system, don't modify schema directly in production

  • Indexes: Index frequently queried columns

  • Prepared Statements: Drizzle automatically uses prepared statements

  • Connection Management: Reuse database connection

  • Studio: Use Drizzle Studio for visual database exploration

  • Error Handling: Handle constraint violations

  • Performance: Use get() for single results instead of all()[0]

Common Patterns

Repository Pattern

export class UserRepository { constructor(private db: ReturnType<typeof drizzle>) {}

async findById(id: string): Promise<User | undefined> { return this.db.select().from(users).where(eq(users.id, id)).get(); }

async findAll(): Promise<User[]> { return this.db.select().from(users); }

async create(data: InsertUser): Promise<User> { return this.db.insert(users).values(data).returning().get(); }

async update(id: string, data: Partial<InsertUser>): Promise<User | undefined> { return this.db.update(users).set(data).where(eq(users.id, id)).returning().get(); }

async delete(id: string): Promise<boolean> { const result = await this.db.delete(users).where(eq(users.id, id)).returning(); return result.length > 0; } }

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

github cli

No summary provided by upstream source.

Repository SourceNeeds Review
General

trpc

No summary provided by upstream source.

Repository SourceNeeds Review
General

keycloak

No summary provided by upstream source.

Repository SourceNeeds Review
General

next.js

No summary provided by upstream source.

Repository SourceNeeds Review