← All articles
DATABASE Drizzle ORM: The SQL-Like TypeScript Database Toolkit 2026-02-15 · 7 min read · drizzle · orm · typescript

Drizzle ORM: The SQL-Like TypeScript Database Toolkit

Database 2026-02-15 · 7 min read drizzle orm typescript database sql serverless migrations type-safety

Drizzle ORM: The SQL-Like TypeScript Database Toolkit

Drizzle ORM logo

Most ORMs try to hide SQL from you. They invent their own query language, generate SQL behind the scenes, and give you an object-oriented abstraction that works well until it does not. Then you spend hours fighting the ORM to produce the query you could have written in thirty seconds.

Drizzle ORM takes the opposite approach. It gives you a TypeScript API that looks and feels like SQL. If you know SQL, you already know Drizzle. Every query maps directly to the SQL it produces, with full type safety inferred from your schema definition. There is no code generation step, no separate schema language, and no heavy runtime. The entire library is under 50KB bundled, making it the most serverless-friendly ORM in the TypeScript ecosystem.

Why Drizzle Is Gaining Traction

Drizzle has grown from a niche library to one of the most popular database tools in the TypeScript world, and the reasons are concrete.

SQL-first design: Drizzle's query API mirrors SQL syntax. select(), from(), where(), leftJoin() -- if you know the SQL equivalent, you know the Drizzle call. There is no new abstraction to learn.

Zero code generation: Prisma requires running prisma generate to produce a client. Drizzle infers all types directly from your TypeScript schema at compile time. Change a column, and every query using that column updates its types instantly.

Tiny bundle size: At roughly 35-50KB gzipped (depending on your database driver), Drizzle adds almost nothing to your deployment. This matters enormously for serverless and edge runtimes where cold start time is a function of bundle size.

Edge and serverless compatibility: Drizzle works with every major serverless database -- Neon, PlanetScale, Turso, Cloudflare D1, Vercel Postgres, and Supabase. It runs on Cloudflare Workers, Vercel Edge Functions, Deno Deploy, and Bun without shims or polyfills.

Relational query API: For developers who prefer a more Prisma-like syntax, Drizzle also offers a relational query API that handles nested includes and eager loading, while still generating efficient SQL under the hood.

Schema Definition

Drizzle schemas are plain TypeScript files. You define tables using builder functions, and the resulting objects carry the type information that powers every query.

// src/db/schema.ts
import { pgTable, serial, text, boolean, timestamp, integer } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: text("email").notNull().unique(),
  name: text("name"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: text("title").notNull(),
  content: text("content"),
  published: boolean("published").default(false).notNull(),
  authorId: integer("author_id")
    .references(() => users.id)
    .notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

This schema is the single source of truth. Drizzle infers the TypeScript types for inserts, selects, and updates from these definitions. You never write a separate interface or type by hand.

To extract types for use elsewhere in your application:

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

type User = InferSelectModel<typeof users>;
type NewUser = InferInsertModel<typeof users>;

Drizzle supports PostgreSQL, MySQL, and SQLite with dedicated packages (drizzle-orm/pg-core, drizzle-orm/mysql-core, drizzle-orm/sqlite-core). The API is nearly identical across all three, with dialect-specific features available where appropriate.

Queries: Select, Insert, and Joins

Basic Queries

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

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

// Select with conditions
const activeAuthors = await db
  .select()
  .from(users)
  .where(like(users.email, "%@example.com"));

// Insert a single row
const newUser = await db
  .insert(users)
  .values({ email: "[email protected]", name: "Alice" })
  .returning();

// Insert multiple rows
await db.insert(posts).values([
  { title: "First Post", authorId: newUser[0].id },
  { title: "Second Post", authorId: newUser[0].id },
]);

// Update
await db
  .update(posts)
  .set({ published: true })
  .where(eq(posts.authorId, newUser[0].id));

// Delete
await db.delete(posts).where(eq(posts.published, false));

Every query returns fully typed results. The allUsers variable is typed as User[] without any assertion or cast.

Joins

Joins in Drizzle look like the SQL you would write by hand:

// Inner join
const userPosts = await db
  .select({
    userName: users.name,
    postTitle: posts.title,
    published: posts.published,
  })
  .from(posts)
  .innerJoin(users, eq(posts.authorId, users.id))
  .where(eq(posts.published, true))
  .orderBy(desc(posts.createdAt));

// Left join with optional fields
const usersWithPosts = await db
  .select({
    user: users,
    postCount: sql<number>`count(${posts.id})`.as("post_count"),
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .groupBy(users.id);

Relational Query API

For nested data loading, Drizzle provides a Prisma-like relational API:

import { relations } from "drizzle-orm";

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

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

// Query with relations
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: [desc(posts.createdAt)],
      limit: 5,
    },
  },
});

This generates efficient SQL with the appropriate joins or subqueries depending on the relationship type.

Migrations with drizzle-kit

Drizzle Kit is the companion CLI for managing database migrations. It reads your TypeScript schema files and generates SQL migration files automatically.

# Install drizzle-kit
bun add -d drizzle-kit

# Generate migrations from schema changes
bunx drizzle-kit generate

# Apply pending migrations
bunx drizzle-kit migrate

# Push schema directly (development only -- skips migration files)
bunx drizzle-kit push

# Open Drizzle Studio (database browser)
bunx drizzle-kit studio

Configuration lives in 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!,
  },
});

The generate command diffs your current schema against the previous migration state and produces a SQL file in the drizzle/ directory. These files are plain SQL -- readable, version-controlled, and editable if you need to customize the migration logic.

For local development, drizzle-kit push is useful. It applies schema changes directly to the database without creating migration files, which speeds up rapid iteration. For production, always use generate followed by migrate so you have an auditable migration history.

Drizzle Studio is a browser-based database GUI that launches locally and gives you a visual interface for browsing data, running queries, and inspecting your schema.

Which One Should You Pick? Drizzle vs. Prisma vs. TypeORM

Drizzle vs. Prisma

Query philosophy: Prisma abstracts away SQL with its own query language. Drizzle gives you SQL expressed as TypeScript. If you think in SQL, Drizzle is more natural. If you prefer a high-level API that hides SQL details, Prisma may feel more productive.

Code generation: Prisma requires running prisma generate after every schema change to produce its client library. Drizzle uses TypeScript inference, so types update the moment you save your schema file. In large projects, eliminating the code generation step removes a common source of stale types and CI complexity.

Bundle size: Prisma's generated client and query engine together add 2-10MB to your deployment. Drizzle adds under 50KB. On serverless platforms with cold start sensitivity, this difference is significant.

Edge compatibility: Prisma added edge support through Prisma Accelerate, a proxy service. Drizzle works natively on edge runtimes with no proxy needed, connecting directly to edge-capable databases.

Migration tooling: Both generate SQL migration files from schema changes. Prisma's migration system is more mature, with features like shadow databases and baseline migrations. Drizzle Kit is simpler and more predictable, though it handles fewer edge cases automatically.

Drizzle vs. TypeORM

TypeScript integration: TypeORM uses decorators and class-based entities, which require experimentalDecorators and emitDecoratorMetadata in your TypeScript config. Drizzle uses plain functions and objects with no decorator magic. The result is better type inference and no runtime metadata overhead.

Performance: TypeORM's Active Record and Data Mapper patterns carry runtime overhead. Drizzle generates lean SQL with minimal abstraction layers, resulting in measurably faster query execution in benchmarks.

Maintenance: TypeORM has had long periods of slow maintenance. Drizzle is under active development with frequent releases and responsive maintainers.

Recommendation: TypeORM made sense when it was the only TypeScript ORM with migration support. Today, both Prisma and Drizzle are better choices for new projects.

Framework Integration

Next.js

// src/db/index.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

Drizzle works in both Next.js Server Components and API Routes without configuration. The Neon serverless driver is a good fit since it communicates over HTTP and works in edge runtimes.

Hono

import { Hono } from "hono";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

type Bindings = { DB: D1Database };

const app = new Hono<{ Bindings: Bindings }>();

app.get("/users", async (c) => {
  const db = drizzle(c.env.DB, { schema });
  const allUsers = await db.select().from(schema.users);
  return c.json(allUsers);
});

This pattern works on Cloudflare Workers with D1, giving you a fully type-safe API with zero external dependencies beyond Hono and Drizzle.

Tips for Getting Started

Start with push in development: Use drizzle-kit push for rapid iteration. Switch to generate + migrate when you are ready to track schema changes formally.

Define relations early: Even if you do not use the relational query API immediately, declaring relations in your schema gives you better tooling support and makes complex queries easier later.

Use the $inferSelect and $inferInsert shortcuts: Every table object exports these types directly, so you can write typeof users.$inferSelect instead of importing InferSelectModel.

Keep schemas modular: Split large schemas across multiple files and re-export them from a barrel file. Drizzle Kit supports multiple schema files via glob patterns in the config.

Enable logging during development: Pass logger: true to the drizzle() constructor to see every SQL query Drizzle generates. This is invaluable for understanding performance and catching N+1 queries.

const db = drizzle(client, { schema, logger: true });

Conclusion

Drizzle ORM fills a gap that has existed in the TypeScript ecosystem for years: a database toolkit that respects SQL rather than hiding it, provides genuine type safety without code generation, and runs anywhere from a Raspberry Pi to a Cloudflare Worker. Its growth is not driven by marketing but by developers discovering that they can write the SQL they already know, get full TypeScript types, and deploy a fraction of the bundle size they were shipping with other ORMs.

The library is still evolving. Features like computed columns, polymorphic relations, and enhanced Drizzle Studio capabilities are on the roadmap. But the core -- schema definition, query building, and migration generation -- is stable and production-ready. If you are starting a new TypeScript project that talks to a database, Drizzle deserves to be your default choice. It gets out of your way and lets you write the SQL you meant to write all along.