JavaScript Development Space

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:

npm install drizzle-orm --save

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)

ts
1 import { pgTable, varchar, serial, primaryKey } from "drizzle-orm/pg-core";
2
3 export const users = pgTable("users", {
4 id: serial("id").primaryKey(),
5 firstName: varchar(),
6 });

2. Initialize Database Connection

ts
1 import { drizzle } from 'drizzle-orm/node-postgres';
2
3 const dbUrl = process.env.DATABASE_URL;
4 const db = drizzle(dbUrl);
5
6 // Optionally, change column naming conventions:
7 const db = drizzle({ connection: dbUrl, casing: 'snake_case' });

3. Simple Select Query

ts
1 const result = await db.select().from(users);
2 // SELECT "id", "first_name" FROM users;

4. Advanced Example: Reuse Structures Between Tables

ts
1 // columns.helpers.ts
2 const timestamps = {
3 updated_at: timestamp(),
4 created_at: timestamp().defaultNow().notNull(),
5 deleted_at: timestamp(),
6 };
7
8 // users.sql.ts
9 export const users = pgTable('users', {
10 id: integer(),
11 ...timestamps,
12 });
13
14 // posts.sql.ts
15 export const posts = pgTable('posts', {
16 id: integer(),
17 ...timestamps,
18 });

Full Schema with References and Indexes

ts
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";
4
5 export const rolesEnum = pgEnum("roles", ["guest", "user", "admin"]);
6
7 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 );
21
22 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 );
35
36 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

ts
1 await db
2 .select()
3 .from(posts)
4 .leftJoin(comments, eq(posts.id, comments.post_id))
5 .where(eq(posts.id, 10));

Advanced Query with Filters

ts
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[] = [];
11
12 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));
15
16 return db
17 .select()
18 .from(products)
19 .where(and(...filters));
20 }

Subqueries Example

ts
1 const subquery = db
2 .select()
3 .from(internalStaff)
4 .leftJoin(customUser, eq(internalStaff.userId, customUser.id))
5 .as('internal_staff');
6
7 const mainQuery = await db
8 .select()
9 .from(ticket)
10 .leftJoin(subquery, eq(subquery.internal_staff.userId, ticket.staffId));

CRUD Operations

Insert:

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

Select:

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

Update:

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

Delete:

ts
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.

JavaScript Development Space

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.