Skip to content

Drizzle ORM

Databases & CachingDatabase/ORMTypeScript

What it is

Drizzle is a TypeScript ORM whose query builder mirrors SQL closely, with no code generation step and a very small runtime suitable for serverless and edge environments.

Define tables as TypeScript objects. The query builder mirrors SQL clauses one-to-one, and drizzle-kit generates migrations from schema changes.

Installation

npm install drizzle-orm && npm install -D drizzle-kit

Getting started

The smallest useful thing you can do with it, and what each part means.

Schema as TypeScript
import { pgTable, serial, text, integer, timestamp } from 'drizzle-orm/pg-core';

export const books = pgTable('books', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  year: integer('year').notNull(),
  authorId: integer('author_id').references(() => authors.id),
  createdAt: timestamp('created_at').defaultNow(),
});

// Types come straight from the definition — no generate step.
type Book = typeof books.$inferSelect;
type NewBook = typeof books.$inferInsert;
Because the schema is ordinary TypeScript, editing it updates the types instantly. There is no generation step to forget to run.
Queries that read like SQL
import { eq, and, gte, desc } from 'drizzle-orm';

const recent = await db
  .select({ id: books.id, title: books.title, author: authors.name })
  .from(books)
  .leftJoin(authors, eq(books.authorId, authors.id))
  .where(and(gte(books.year, 1990), eq(books.published, true)))
  .orderBy(desc(books.year))
  .limit(10);
The builder maps directly onto SQL clauses, so anyone who knows SQL can read it — and the explicit select object means the result type contains exactly those columns.

Advanced usage

Where the library earns its place over a simpler alternative.

Transactions and prepared statements
await db.transaction(async (tx) => {
  await tx.update(stock)
    .set({ quantity: sql`${stock.quantity} - ${qty}` })
    .where(eq(stock.sku, sku));

  await tx.insert(orders).values({ sku, qty });
});

// Prepared statements are compiled once — valuable on hot paths.
const byYear = db.select().from(books)
  .where(gte(books.year, sql.placeholder('year')))
  .prepare('books_by_year');

const rows = await byYear.execute({ year: 1990 });
The sql template tag drops to raw SQL where the builder cannot express something, while still parameterising values safely.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Types are unexpectedly wide
Use an explicit select object rather than select() with no argument, so the result type lists only the columns you asked for.
Migrations do not reflect a schema change
drizzle-kit reads the schema path from drizzle.config.ts. Confirm the path and re-run generate.

Best practices

  • Prefer Drizzle over Prisma when cold start or bundle size matters — serverless and edge in particular.
  • Use $inferSelect and $inferInsert rather than hand-writing row types.
  • Generate migrations with drizzle-kit and review the SQL before applying.
  • Use the sql template tag for expressions the builder cannot represent, never string concatenation.

Background

Why it exists, and what it was reacting to.

Drizzle was built for developers who found Prisma's generated client and Rust engine too heavy — particularly in serverless, where cold starts and bundle size matter. Schemas are plain TypeScript, so types are immediate.