A concise, friendly walkthrough for getting productive with Drizzle in TypeScript.
Drizzle is a type-safe ORM built for TypeScript-first teams. It combines a SQL-like API with excellent TypeScript inference, small runtime overhead, and a migration system that keeps schema changes pleasant.
Focus areas include type safety, familiar SQL-like syntax, performance, and migrations.
Drizzle infers types from your table schema so your queries stay typed end-to-end.
<span style="color:#7dd3fc">import</span> <span style="color:#c792ea">{ pgTable, serial, text, varchar }</span> <span style="color:#7dd3fc">from</span> <span style="color:#8bd5a6">'drizzle-orm/pg-core'</span>;
const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
});
<span style="color:#ffcb6b">type</span> User = typeof users.$inferSelect; // { id: number, name: string, email: string }
<span style="color:#ffcb6b">type</span> NewUser = typeof users.$inferInsert; // { name: string, email: string }Queries feel familiar if you know SQL, with helper functions for joins, filters, and aggregates.
const result = await db
.select()
.from(users)
.where(eq(users.email, 'user@example.com'))
.limit(1);
const userPosts = await db
.select({ userName: users.name, postTitle: posts.title })
.from(users)
.innerJoin(posts, eq(users.id, posts.authorId));npm install drizzle-orm
npm install -D drizzle-kitA concise schema file and a drizzle connection are all you need to start.
// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id').references(() => users.id),
});// db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
export const db = drizzle(pool, { schema });Here are common create/read/update/delete operations.
// Create
const newUser = await db.insert(users).values({ name: 'John Doe', email: 'john@example.com' }).returning();
// Read
const allUsers = await db.select().from(users);
const user = await db.select().from(users).where(eq(users.id, 1));
// Update
await db.update(users).set({ name: 'Jane Doe' }).where(eq(users.id, 1));
// Delete
await db.delete(users).where(eq(users.id, 1));Drizzle ships with a CLI (drizzle-kit) for generation and a migrator for applying changes.
{
"out": "./drizzle",
"schema": "./db/schema.ts",
"breakpoints": true
}npx drizzle-kit generate:pg
import { migrate } from 'drizzle-orm/node-postgres/migrator';
await migrate(db, { migrationsFolder: './drizzle' });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] }),
}));await db.transaction(async (tx) => {
const user = await tx.insert(users).values({ name: 'Alice', email: 'alice@example.com' }).returning();
await tx.insert(posts).values({ title: 'First Post', authorId: user[0].id });
});const userStats = await db
.select({ userId: users.id, postCount: count(posts.id), latestPost: max(posts.createdAt) })
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.groupBy(users.id);Drizzle sits between low-level query builders and full "magic" ORMs. Here are practical tradeoffs.
| Feature | Drizzle | Prisma | TypeORM |
|---|---|---|---|
| Type Safety | ★★★★★ | ★★★★★ | ★★★ |
| Performance | ★★★★★ | ★★★ | ★★★ |
| Learning Curve | ★★★★ | ★★★ | ★★★★ |
Official docs and examples are excellent first stops.