Builds type-safe database applications with Drizzle ORM including schema definition, queries, relations, and migrations. Use when working with databases in TypeScript, defining schemas, writing type-safe queries, or managing migrations.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add mgd34msu/goodvibes-gemini --skill drizzle --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Drizzle?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mgd34msu-drizzle)More formats (shields.io, HTML) on the badges page.
---
name: drizzle
description: Builds type-safe database applications with Drizzle ORM including schema definition, queries, relations, and migrations. Use when working with databases in TypeScript, defining schemas, writing type-safe queries, or managing migrations.
---
# Drizzle ORM
Lightweight, type-safe TypeScript ORM with zero dependencies and SQL-first approach.
## Quick Start
**Install:**
```bash
# PostgreSQL
npm install drizzle-orm postgres
npm install -D drizzle-kit
# MySQL
npm install drizzle-orm mysql2
npm install -D drizzle-kit
# SQLite
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
```
**Project structure:**
```
src/
db/
index.ts # Database connection
schema.ts # Table definitions
relations.ts # Relation definitions
drizzle.config.ts # Drizzle Kit config
```
## Database Connection
### PostgreSQL
```typescript
// db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
export const db = drizzle(client, { schema });
```
### MySQL
```typescript
// db/index.ts
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
database: 'mydb',
});
export const db = drizzle(connection, { schema });
```
### SQLite
```typescript
// db/index.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 });
```
### Turso (Edge SQLite)
```typescript
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 });
```
## Schema Definition
### PostgreSQL Tables
```typescript
// db/schema.ts
import {
pgTable,
serial,
varchar,
text,
integer,
boolean,
timestamp,
uuid,
jsonb,
} from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 100 }),
bio: text('bio'),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: uuid('id').primaryKey().defaultRandom(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
metadata: jsonb('metadata'),
createdAt: timestamp('created_at').defaultNow(),
});
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: uuid('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
```
### MySQL Tables
```typescript
import {
mysqlTable,
serial,
varchar,
text,
int,
boolean,
timestamp,
json,
} from 'drizzle-orm/mysql-core';
export const users = mysqlTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 100 }),
createdAt: timestamp('created_at').defaultNow(),
});
```
### SQLite Tables
```typescript
import {
sqliteTable,
integer,
text,
} from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: integer('created_at', { mode: 'timestamp' })
.$defaultFn(() => new Date()),
});
```
### Type Inference
```typescript
// Infer types from schema
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
```
## Relations
```typescript
// db/relations.ts
import { relations } from 'drizzle-orm';
import { users, posts, comments } from './schema';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
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.authorId],
references: [users.id],
}),
}));
```
### Many-to-Many
```typescript
// Schema
export const usersToGroups = pgTable('users_to_groups', {
userId: integer('user_id')
.notNull()
.references(() => users.id),
groupId: integer('group_id')
.notNull()
.references(() => groups.id),
}, (t) => ({
pk: primaryKey({ columns: [t.userId, t.groupId] }),
}));
// Relations
export const usersRelations = relations(users, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const groupsRelations = relations(groups, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const usersToGroupsRelations = relations(usersToGroups, ({ one }) => ({
user: one(users, {
fields: [usersToGroups.userId],
references: [users.id],
}),
group: one(groups, {
fields: [usersToGroups.groupId],
references: [groups.id],
}),
}));
```
## Queries
### Select
```typescript
import { eq, lt, gte, ne, and, or, like, ilike, between, inArray } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './db/schema';
// Select all
const allUsers = await db.select().from(users);
// Select specific columns
const userNames = await db.select({
id: users.id,
name: users.name,
}).from(users);
// Where clause
const activeUsers = await db.select()
.from(users)
.where(eq(users.isActive, true));
// Multiple conditions
const filteredUsers = 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.name, 'John'),
eq(users.name, 'Jane'),
)
);
// Pattern matching
const matching = await db.select()
.from(users)
.where(like(users.email, '%@gmail.com'));
// Case-insensitive (PostgreSQL)
const matchingCI = await db.select()
.from(users)
.where(ilike(users.name, '%john%'));
// Range queries
const inRange = await db.select()
.from(users)
.where(between(users.age, 18, 65));
// IN operator
const specificUsers = await db.select()
.from(users)
.where(inArray(users.id, [1, 2, 3]));
```
### Joins
```typescript
import { eq } from 'drizzle-orm';
// Inner join
const postsWithAuthors = await db.select({
post: posts,
author: users,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id));
// Left join
const usersWithPosts = await db.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
// Multiple joins
const fullData = await db.select()
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.leftJoin(comments, eq(posts.id, comments.postId));
```
### Ordering and Pagination
```typescript
import { asc, desc } from 'drizzle-orm';
// Order by
const orderedUsers = await db.select()
.from(users)
.orderBy(desc(users.createdAt));
// Multiple order columns
const sorted = await db.select()
.from(users)
.orderBy(asc(users.name), desc(users.createdAt));
// Pagination
const page = await db.select()
.from(users)
.limit(10)
.offset(20);
```
### Aggregations
```typescript
import { count, sum, avg, min, max } from 'drizzle-orm';
// Count
const userCount = await db.select({
count: count(),
}).from(users);
// Group by
const postsByAuthor = await db.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId);
// With having
const activeAuthors = await db.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId)
.having(({ postCount }) => gte(postCount, 5));
```
### Relational Queries
```typescript
// Find many with relations
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// Nested relations
const postsWithDetails = await db.query.posts.findMany({
with: {
author: true,
comments: {
with: {
author: true,
},
},
},
});
// Find first
const user = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, 1),
with: {
posts: true,
},
});
// Select specific columns
const partialPosts = await db.query.posts.findMany({
columns: {
id: true,
title: true,
},
with: {
author: {
columns: {
name: true,
},
},
},
});
// Filter nested relations
const recentPosts = await db.query.users.findMany({
with: {
posts: {
where: (posts, { eq }) => eq(posts.published, true),
orderBy: (posts, { desc }) => [desc(posts.createdAt)],
limit: 5,
},
},
});
```
## Insert
```typescript
// Single insert
await db.insert(users).values({
email: 'john@example.com',
name: 'John Doe',
});
// Insert with returning (PostgreSQL, SQLite)
const [newUser] = await db.insert(users)
.values({
email: 'jane@example.com',
name: 'Jane Doe',
})
.returning();
// Multiple insert
await db.insert(users).values([
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
]);
// Upsert (on conflict)
await db.insert(users)
.values({
email: 'john@example.com',
name: 'John Updated',
})
.onConflictDoUpdate({
target: users.email,
set: { name: 'John Updated' },
});
// On conflict do nothing
await db.insert(users)
.values({ email: 'john@example.com', name: 'John' })
.onConflictDoNothing({ target: users.email });
```
## Update
```typescript
// Update with where
await db.update(users)
.set({ name: 'John Smith' })
.where(eq(users.id, 1));
// Update with returning
const [updated] = await db.update(users)
.set({ isActive: false })
.where(eq(users.id, 1))
.returning();
// Increment value
await db.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, 1));
```
## Delete
```typescript
// Delete with where
await db.delete(users).where(eq(users.id, 1));
// Delete with returning
const [deleted] = await db.delete(users)
.where(eq(users.id, 1))
.returning();
// Delete all (be careful!)
await db.delete(users);
```
## Transactions
```typescript
// Basic transaction
await db.transaction(async (tx) => {
const [user] = await tx.insert(users)
.values({ email: 'new@example.com', name: 'New User' })
.returning();
await tx.insert(posts).values({
title: 'First Post',
authorId: user.id,
});
});
// With rollback
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: 'test@example.com' });
// Rollback on condition
if (someCondition) {
tx.rollback();
}
await tx.insert(posts).values({ title: 'Post', authorId: 1 });
});
// Nested transactions (savepoints)
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: 'outer@example.com' });
await tx.transaction(async (tx2) => {
await tx2.insert(posts).values({ title: 'Nested', authorId: 1 });
});
});
```
## Migrations
### Configuration
```typescript
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
```
### Commands
```bash
# Generate migration
npx drizzle-kit generate
# Apply migrations
npx drizzle-kit migrate
# Push schema (dev, no migration files)
npx drizzle-kit push
# Pull existing database schema
npx drizzle-kit pull
# Open Drizzle Studio
npx drizzle-kit studio
```
## Raw SQL
```typescript
import { sql } from 'drizzle-orm';
// Raw query
const result = await db.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
// In select
const users = await db.select({
id: users.id,
fullName: sql<string>`${users.firstName} || ' ' || ${users.lastName}`,
}).from(users);
// Typed raw
const count = await db.execute<{ count: number }>(
sql`SELECT COUNT(*) as count FROM users`
);
```
## Prepared Statements
```typescript
import { placeholder } from 'drizzle-orm';
// Prepare statement
const prepared = db.select()
.from(users)
.where(eq(users.id, placeholder('id')))
.prepare('get_user');
// Execute with values
const user = await prepared.execute({ id: 1 });
// Relational prepared
const preparedWithPosts = db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, placeholder('id')),
with: { posts: true },
}).prepare('user_with_posts');
const result = await preparedWithPosts.execute({ id: 1 });
```
## Best Practices
1. **Export types from schema** - Use $inferSelect and $inferInsert
2. **Use relations for nested data** - Single query, no N+1
3. **Prepare frequent queries** - Better performance
4. **Use transactions for multi-step ops** - Ensure consistency
5. **Leverage push for development** - Faster iteration
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Missing schema in drizzle() | Pass { schema } to enable query API |
| Not exporting relations | Export from schema file |
| Using select() for relations | Use db.query for relational queries |
| Raw SQL without typing | Add type parameter to sql`` |
| Forgetting returning() | Add .returning() for inserted data |
## Reference Files
- [references/queries.md](references/queries.md) - Advanced query patterns
- [references/migrations.md](references/migrations.md) - Migration workflows
- [references/relations.md](references/relations.md) - Complex relations
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!