Drizzle ORM: A Modern, Type-Safe ORM for JavaScript/TypeScript
Drizzle ORM has rapidly gained popularity within the JavaScript and TypeScript communities. Known for its compact size and strong type-safety, Drizzle provides an efficient way to manage relational databases like PostgreSQL, MySQL, and SQLite. It’s the second most desired ORM based on developer surveys and was ranked 27th in the JavaScript Rising Stars of 2024. This article explores what makes Drizzle ORM a powerful tool, its installation process, and how to get started with using it effectively in your projects.
What Is Drizzle ORM?
Drizzle is a lightweight and modern ORM designed for both JavaScript and TypeScript environments. It supports all major relational databases and is known for its small footprint (around 7.4 KB) and lack of external dependencies. Drizzle is especially well-suited for serverless environments, making it a compelling alternative to traditional ORMs like Sequelize and TypeORM.
Key Features:
- Lightweight: With a size of ~7.4 KB, Drizzle is one of the lightest ORM libraries available.
- Type Safety: Strong typing ensures your database schema and queries are fully typed, reducing errors.
- Environment Compatibility: Works seamlessly in Node.js, serverless environments, and even browsers.
- CLI Tools: Drizzle Kit provides a robust CLI for database migrations and seeding.
- Graphical Interface: Drizzle Studio lets you manage your database visually, ideal for projects like Neon or Turso.
How to Install Drizzle ORM
To get started with Drizzle ORM, simply run:
For pnpm or Yarn, the process is the same. Don’t forget to install the appropriate driver for your database, such as pg
for PostgreSQL or sqlite3
for SQLite.
Example Usage
1. Declare a Schema (Postgres)
1 import { pgTable, varchar, serial, primaryKey } from "drizzle-orm/pg-core";23 export const users = pgTable("users", {4 id: serial("id").primaryKey(),5 firstName: varchar(),6 });
2. Initialize Database Connection
1 import { drizzle } from 'drizzle-orm/node-postgres';23 const dbUrl = process.env.DATABASE_URL;4 const db = drizzle(dbUrl);56 // Optionally, change column naming conventions:7 const db = drizzle({ connection: dbUrl, casing: 'snake_case' });
3. Simple Select Query
1 const result = await db.select().from(users);2 // SELECT "id", "first_name" FROM users;
4. Advanced Example: Reuse Structures Between Tables
1 // columns.helpers.ts2 const timestamps = {3 updated_at: timestamp(),4 created_at: timestamp().defaultNow().notNull(),5 deleted_at: timestamp(),6 };78 // users.sql.ts9 export const users = pgTable('users', {10 id: integer(),11 ...timestamps,12 });1314 // posts.sql.ts15 export const posts = pgTable('posts', {16 id: integer(),17 ...timestamps,18 });
Full Schema with References and Indexes
1 import { AnyPgColumn } from "drizzle-orm/pg-core";2 import { pgEnum, pgTable as table } from "drizzle-orm/pg-core";3 import * as t from "drizzle-orm/pg-core";45 export const rolesEnum = pgEnum("roles", ["guest", "user", "admin"]);67 export const users = table(8 "users",9 {10 id: t.integer().primaryKey().generatedAlwaysAsIdentity(),11 firstName: t.varchar("first_name", { length: 256 }),12 lastName: t.varchar("last_name", { length: 256 }),13 email: t.varchar().notNull(),14 invitee: t.integer().references(() => users.id),15 role: rolesEnum().default("guest"),16 },17 (table) => [18 t.uniqueIndex("email_idx").on(table.email),19 ]20 );2122 export const posts = table(23 "posts",24 {25 id: t.integer().primaryKey().generatedAlwaysAsIdentity(),26 slug: t.varchar().$default(() => generateUniqueString(16)),27 title: t.varchar({ length: 256 }),28 ownerId: t.integer("owner_id").references(() => users.id),29 },30 (table) => [31 t.uniqueIndex("slug_idx").on(table.slug),32 t.index("title_idx").on(table.title),33 ]34 );3536 export const comments = table("comments", {37 id: t.integer().primaryKey().generatedAlwaysAsIdentity(),38 text: t.varchar({ length: 256 }),39 postId: t.integer("post_id").references(() => posts.id),40 ownerId: t.integer("owner_id").references(() => users.id),41 });
Queries and CRUD Operations
Drizzle ORM features a SQL builder that feels similar to Knex.js or TypeORM’s QueryBuilder.
Basic Select Query
1 await db2 .select()3 .from(posts)4 .leftJoin(comments, eq(posts.id, comments.post_id))5 .where(eq(posts.id, 10));
Advanced Query with Filters
1 async function getProductsBy({2 name,3 category,4 maxPrice,5 }: {6 name?: string;7 category?: string;8 maxPrice?: string;9 }) {10 const filters: SQL[] = [];1112 if (name) filters.push(ilike(products.name, name));13 if (category) filters.push(eq(products.category, category));14 if (maxPrice) filters.push(lte(products.price, maxPrice));1516 return db17 .select()18 .from(products)19 .where(and(...filters));20 }
Subqueries Example
1 const subquery = db2 .select()3 .from(internalStaff)4 .leftJoin(customUser, eq(internalStaff.userId, customUser.id))5 .as('internal_staff');67 const mainQuery = await db8 .select()9 .from(ticket)10 .leftJoin(subquery, eq(subquery.internal_staff.userId, ticket.staffId));
CRUD Operations
Insert:
1 await db.insert(users).values({ name: "Masha" });
Select:
1 const result = await db.select().from(users);
Update:
1 await db.update(users).set({ name: "Vasya" }).where(users.name.eq("Masha"));
Delete:
1 await db.delete(users).where(users.name.eq("Vasya"));
Conclusion
Drizzle ORM stands out for its simplicity and performance, offering a modern, type-safe approach to working with relational databases. Whether you're building a traditional web app or a serverless system, Drizzle provides an elegant and lightweight solution for all your database needs. The intuitive syntax and powerful features, such as support for subqueries, make it a great tool for both novice and advanced developers.