Drizzle ORM: A Modern, Type‑Safe ORM for JavaScript/TypeScript

February, 13th 2025 4 min read

Drizzle ORM has grown rapidly within the JavaScript and TypeScript ecosystem. Its design prioritizes type‑safety, simplicity, and performance—offering a powerful alternative to heavy, dependency‑driven ORMs. Drizzle is compact, predictable, and integrates naturally into modern backend environments like Node.js, serverless platforms, and edge runtimes.

This guide provides a practical, updated overview of Drizzle ORM: what it does well, how to install it, how to define schemas, and how to write clean, type‑safe queries.

What Is Drizzle ORM?

Drizzle is a lightweight relational ORM built for JavaScript and TypeScript. Supporting PostgreSQL, MySQL, and SQLite, it emphasizes stability, small bundle size, and strong typing. Its API resembles SQL more than traditional Active‑Record patterns, making queries explicit and predictable.

Key Features

  • Compact & dependency‑free — around 7.4 KB.
  • Fully typed — schemas, queries, and relations all use TypeScript inference.
  • Environment‑ready — works in Node.js, serverless, edge runtimes, and browsers.
  • Migrations — Drizzle Kit provides easy schema migrations and introspection.
  • Graphical Studio — a visual database manager suitable for PostgreSQL, Neon, PlanetScale, and Turso.

Installing Drizzle ORM

Use your preferred package manager:

bash
npm install drizzle-orm

Install the appropriate database driver:

bash
npm install pg          # PostgreSQL
npm install mysql2      # MySQL
npm install better-sqlite3 # SQLite

If using Drizzle Kit for migrations:

bash
npm install -D drizzle-kit

Defining a Schema (PostgreSQL Example)

Schemas in Drizzle are declared via functions that closely mimic SQL structure.

ts
import { pgTable, serial, varchar } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  firstName: varchar("first_name", { length: 256 }),
});

Schemas are fully typed—queries against users.firstName will autocomplete and validate types.


Database Initialization

Establishing a connection is straightforward:

ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Client } from "pg";

const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();

export const db = drizzle(client);

You can optionally set casing conventions:

ts
const db = drizzle(client, { casing: "snake_case" });

Simple Select Query

ts
const rows = await db.select().from(users);

Produces SQL similar to:

sql
SELECT "id", "first_name" FROM "users";

Shared Structures Between Tables

Use helpers to reduce duplication:

ts
// columns.helpers.ts
import { timestamp } from "drizzle-orm/pg-core";

export const timestamps = {
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at"),
  deletedAt: timestamp("deleted_at"),
};
ts
// users.sql.ts
import { pgTable, integer } from "drizzle-orm/pg-core";
import { timestamps } from "./columns.helpers";

export const users = pgTable("users", {
  id: integer("id").primaryKey(),
  ...timestamps,
});

Full Schema with Relations, Enums & Indexes

ts
import * as t from "drizzle-orm/pg-core";
import { pgEnum, pgTable as table } from "drizzle-orm/pg-core";

export const roles = pgEnum("roles", ["guest", "user", "admin"]);

export const users = table(
  "users",
  {
    id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
    firstName: t.varchar("first_name", { length: 256 }),
    lastName: t.varchar("last_name", { length: 256 }),
    email: t.varchar().notNull(),
    role: roles().default("guest"),
    invitee: t.integer().references(() => users.id),
  },
  table => [t.uniqueIndex("email_idx").on(table.email)]
);

export const posts = table(
  "posts",
  {
    id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
    slug: t.varchar("slug").$default(() => crypto.randomUUID()),
    title: t.varchar({ length: 256 }),
    ownerId: t.integer("owner_id").references(() => users.id),
  },
  table => [
    t.uniqueIndex("slug_idx").on(table.slug),
    t.index("title_idx").on(table.title),
  ]
);

export const comments = table("comments", {
  id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
  text: t.varchar({ length: 256 }),
  postId: t.integer("post_id").references(() => posts.id),
  ownerId: t.integer("owner_id").references(() => users.id),
});

Select Queries & Joins

Basic Left Join

ts
const result = await db
  .select()
  .from(posts)
  .leftJoin(comments, (c, p) => c.postId.eq(p.id))
  .where(posts.id.eq(10));

Dynamic Filters

ts
import { and, ilike, eq, lte } from "drizzle-orm";

async function getProducts({ name, category, maxPrice }) {
  const filters = [];

  if (name) filters.push(ilike(products.name, `%${name}%`));
  if (category) filters.push(eq(products.category, category));
  if (maxPrice) filters.push(lte(products.price, maxPrice));

  return db.select().from(products).where(and(...filters));
}

Subqueries

ts
const staffView = db
  .select()
  .from(internalStaff)
  .leftJoin(customUser, (s, u) => s.userId.eq(u.id))
  .as("staff_view");

const tickets = await db
  .select()
  .from(ticket)
  .leftJoin(staffView, (v, t) => v.internal_staff.userId.eq(t.staffId));

CRUD Operations

Insert

ts
await db.insert(users).values({ firstName: "Masha" });

Select

ts
const allUsers = await db.select().from(users);

Update

ts
await db.update(users).set({ firstName: "Vasya" }).where(eq(users.firstName, "Masha"));

Delete

ts
await db.delete(users).where(eq(users.firstName, "Vasya"));

Conclusion

Drizzle ORM succeeds by being small, predictable, and type‑accurate. Unlike traditional ORMs, it avoids magic abstractions and instead exposes SQL-like primitives with strong TypeScript inference. Whether you are building an API server, a serverless function, or an edge application, Drizzle offers the performance and clarity needed to work efficiently with relational data.

Its combination of type‑safe schemas, clear migration tools, and SQL‑first query builder makes it one of the most practical ORMs available for modern JavaScript and TypeScript development.