Skip to main content

Overview

Drizzle ORM provides a type-safe query builder for all CRUD operations. Queries are built using a fluent, chainable API that mirrors SQL syntax.

Select Queries

Basic Select

Select all columns and rows from a table:
import { db } from './db';
import { users } from './schema';

// Select all users
const allUsers = await db.select().from(users);
// Type: { id: number; name: string; email: string }[]

Select Specific Columns

Choose which columns to return:
// Select specific columns
const userNames = await db.select({ 
  id: users.id, 
  name: users.name 
}).from(users);
// Type: { id: number; name: string }[]

// Use expressions
import { sql } from 'drizzle-orm';

const usersWithLowerEmail = await db.select({
  id: users.id,
  name: users.name,
  lowerEmail: sql<string>`lower(${users.email})`.as('lower_email'),
}).from(users);

Where Conditions

Filter results using conditions:
import { eq, gt, lt, gte, lte, ne, and, or, like, inArray } from 'drizzle-orm';

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

// Comparisons
const adults = await db.select()
  .from(users)
  .where(gte(users.age, 18));

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

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

// Pattern matching
const gmailUsers = await db.select()
  .from(users)
  .where(like(users.email, '%@gmail.com'));

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

Ordering

import { asc, desc } from 'drizzle-orm';

// Ascending order
const usersByName = await db.select()
  .from(users)
  .orderBy(asc(users.name));

// Descending order
const latestUsers = await db.select()
  .from(users)
  .orderBy(desc(users.createdAt));

// Multiple columns
const sortedUsers = await db.select()
  .from(users)
  .orderBy(asc(users.role), desc(users.createdAt));

Limit and Offset

// Pagination
const page1 = await db.select()
  .from(users)
  .limit(10)
  .offset(0);

const page2 = await db.select()
  .from(users)
  .limit(10)
  .offset(10);

Distinct

// Select unique values
const uniqueRoles = await db.selectDistinct({ role: users.role })
  .from(users);

// PostgreSQL: distinct on specific columns
import { eq } from 'drizzle-orm';

const latestPerRole = await db.selectDistinctOn([users.role])
  .from(users)
  .orderBy(users.role, desc(users.createdAt));

Joins

Inner Join

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

// Join users with their posts
const usersWithPosts = await db.select({
  userId: users.id,
  userName: users.name,
  postId: posts.id,
  postTitle: posts.title,
})
  .from(users)
  .innerJoin(posts, eq(users.id, posts.authorId));

Left Join

// Include users even if they have no posts
const allUsersWithPosts = await db.select({
  userId: users.id,
  userName: users.name,
  postId: posts.id,
  postTitle: posts.title,
})
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));
// postId and postTitle will be null for users without posts

Multiple Joins

import { users, posts, comments } from './schema';

const postsWithAuthorsAndComments = await db.select()
  .from(posts)
  .leftJoin(users, eq(posts.authorId, users.id))
  .leftJoin(comments, eq(posts.id, comments.postId));

Insert Queries

Insert Single Row

// Insert one user
const result = await db.insert(users).values({
  name: 'John Doe',
  email: '[email protected]',
});

// Insert with returning
const [newUser] = await db.insert(users).values({
  name: 'Jane Doe',
  email: '[email protected]',
}).returning();
// Type: { id: number; name: string; email: string }

// Return specific columns
const [userId] = await db.insert(users).values({
  name: 'Bob',
  email: '[email protected]',
}).returning({ id: users.id });

Insert Multiple Rows

// Batch insert
const newUsers = await db.insert(users).values([
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' },
  { name: 'Charlie', email: '[email protected]' },
]).returning();

Upsert (On Conflict)

// PostgreSQL and SQLite
const [user] = await db.insert(users)
  .values({ email: '[email protected]', name: 'John' })
  .onConflictDoUpdate({
    target: users.email,
    set: { name: 'John Updated' },
  })
  .returning();

// MySQL
const result = await db.insert(users)
  .values({ email: '[email protected]', name: 'John' })
  .onDuplicateKeyUpdate({
    set: { name: 'John Updated' },
  });

Update Queries

Update Rows

import { eq } from 'drizzle-orm';

// Update specific user
const result = await db.update(users)
  .set({ name: 'John Updated' })
  .where(eq(users.id, 1));

// Update with returning
const [updatedUser] = await db.update(users)
  .set({ name: 'Jane Updated', email: '[email protected]' })
  .where(eq(users.id, 2))
  .returning();

// Update multiple rows
const updated = await db.update(users)
  .set({ active: false })
  .where(lt(users.lastLoginAt, new Date('2023-01-01')));

Update with Expressions

import { sql } from 'drizzle-orm';

// Increment a counter
await db.update(posts)
  .set({ views: sql`${posts.views} + 1` })
  .where(eq(posts.id, 1));

Delete Queries

Delete Rows

import { eq, lt } from 'drizzle-orm';

// Delete specific user
const result = await db.delete(users)
  .where(eq(users.id, 1));

// Delete with returning
const [deletedUser] = await db.delete(users)
  .where(eq(users.id, 2))
  .returning();

// Delete multiple rows
const deleted = await db.delete(users)
  .where(lt(users.createdAt, new Date('2023-01-01')));
Be careful when deleting without a WHERE clause - it will delete all rows in the table!

Aggregations

Count, Sum, Avg

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

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

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

// Sum
const [{ totalViews }] = await db.select({ 
  totalViews: sum(posts.views) 
}).from(posts);

// Average
const [{ avgAge }] = await db.select({ 
  avgAge: avg(users.age) 
}).from(users);

// Min and Max
const [{ oldest, youngest }] = await db.select({ 
  oldest: max(users.age),
  youngest: min(users.age),
}).from(users);

Group By

import { count } from 'drizzle-orm';

// Count users by role
const usersByRole = await db.select({
  role: users.role,
  count: count(),
})
  .from(users)
  .groupBy(users.role);

// Average age by city
const avgAgeByCity = await db.select({
  city: users.city,
  avgAge: avg(users.age),
})
  .from(users)
  .groupBy(users.city);

Having

import { count, gt } from 'drizzle-orm';

// Cities with more than 100 users
const popularCities = await db.select({
  city: users.city,
  count: count(),
})
  .from(users)
  .groupBy(users.city)
  .having(({ count }) => gt(count, 100));

Relational Queries

With schema relations defined, use the simpler relational query API:
import { db } from './db';

// Find user with posts
const user = await db.query.users.findFirst({
  where: (users, { eq }) => eq(users.id, 1),
  with: {
    posts: true,
  },
});

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

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

Subqueries

import { sql } from 'drizzle-orm';

// Subquery in WHERE
const activeUserIds = db.select({ id: users.id })
  .from(users)
  .where(eq(users.active, true));

const postsFromActiveUsers = await db.select()
  .from(posts)
  .where(inArray(posts.authorId, activeUserIds));

// Subquery with alias
const sq = db.$with('sq').as(
  db.select({ id: users.id, name: users.name })
    .from(users)
    .where(eq(users.active, true))
);

const result = await db.with(sq)
  .select()
  .from(sq);

Raw SQL

Execute raw SQL when needed:
import { sql } from 'drizzle-orm';

// Raw query
const result = await db.execute(sql`SELECT * FROM users WHERE id = ${1}`);

// Raw SQL in expressions
const users = await db.select({
  id: users.id,
  upperName: sql<string>`upper(${users.name})`,
}).from(users);

Prepared Statements

Optimize repeated queries:
import { placeholder } from 'drizzle-orm';

// Prepare a query
const prepared = db.select()
  .from(users)
  .where(eq(users.id, placeholder('id')))
  .prepare('get_user_by_id');

// Execute with different values
const user1 = await prepared.execute({ id: 1 });
const user2 = await prepared.execute({ id: 2 });

Best Practices

  • Type safety: Let TypeScript infer types from your queries
  • Prepared statements: Use for frequently executed queries
  • Batch operations: Insert/update multiple rows in a single query
  • Indexes: Add indexes for frequently filtered/joined columns
  • Select only needed columns: Reduces data transfer and processing
  • Always use parameterized queries to prevent SQL injection
  • Be cautious with DELETE and UPDATE without WHERE clauses
  • Use transactions for operations that must succeed or fail together

Next Steps

Relations

Define and query table relationships

Transactions

Learn about database transactions

Build docs developers (and LLMs) love