Editor’s note: This article was updated on 18 July 2024 by Ikeh Akinyemi to match the format of LogRocket’s adoption guides content category. In addition to reviewing and updating the full article, sections were added to cover Drizzle’s background, how it works, reasons why and why not to use Drizzle, key features, use cases, and more.
Drizzle is an emerging TypeScript ORM tool that offers several advantages over Prisma. While Prisma is known for its type safety and easy-to-use API, Drizzle offers a more flexible and extensible solution, specifically when handling complex queries with multiple JOINs. The main philosophy behind Drizzle is that if you know SQL, you know Drizzle.
In this article, we’ll explore Drizzle’s background, features, and considerations to help you decide whether it’s the right ORM for your next project. We’ll also set up a demo project to see Drizzle in action, then compare it to other options to further assist you in your decision.
Drizzle ORM is a lightweight, type-safe, and developer-friendly object-relational mapping (ORM) tool designed for TypeScript and JavaScript. The Drizzle Team developed this ORM with zero dependencies, resulting in a lightweight and performant tool that’s serverless-ready by design.
The aim of Drizzle is to simplify database interactions for developers with a flexible, intuitive and type-safe query builder that closely mimics SQL syntax while leveraging TypeScript’s type system for its safety. Its type safety system is ensured from the schema definition to query execution.
Drizzle’s query builder uses method chaining to construct SQL queries. As you build your query, Drizzle infers the types of the columns you’re selecting or filtering on, providing real-time type checking and autocompletion in your IDE.
This approach helps catch potential errors early in your development process, and as a result improves overall code quality. For example:
const users = await db.select().from(usersTable).where(eq(usersTable.id, 1));
While Drizzle provides you with a powerful query builder, it also allows you to write raw SQL when needed. This flexibility enables you to optimize complex queries or use database-specific features.
Drizzle uses TypeScript to define database schema, allowing you to create table definitions that closely looks like SQL CREATE TABLE
statements. It also leverages TypeScript’s type inference capabilities to provide type safety throughout your queries, so you get autocompletion and type checking for your table columns, relationships, and query results.
Drizzle works with various database drivers and doesn’t manage connections itself. This allows you to use your preferred connection pooling solution. Also, Drizzle includes a separate package called Drizzle Kit for handling schema migrations, but applying migrations is left to the developer.
One of Drizzle’s key strengths is its simplicity and flexibility. Its API is designed to be straightforward and easy to understand, allowing you to perform joins, aggregations, advanced filtering, and much more.
However, Drizzle’s simplicity doesn’t mean it compromises on functionality:
You can easily integrate Drizzle into your serverless functions without worrying about long startup times or excessive resource usage. Also, you can use both relational and SQL-like query APIs to access your relational data. As a bonus, if you’re well-versed in SQL syntax already, you’ll find the learning curve to be fairly low.
Drizzle has a growing community of developers adopting the ORM into their projects. One of the contributing reasons to that is the comprehensive and developer-friendly documentation, covering the essential usage of Drizzle like its installation and usage examples, and the active Drizzle Team.
You’ve seen the different advantages Drizzle offers, but it’s also important to consider possible drawbacks before adopting it in your project.
One current limitation of Drizzle is the lack of built-support for database migrations. Unlike other existing ORMs that provide you with a migration tool out of the box, Drizzle currently relies on you to handle migrations manually or use external libraries, so migrating your project can be a challenge.
Vendor lock-in is another aspect to keep in your mind when choosing an ORM. Although Drizzle supports multiple databases, committing to a specific ORM always involves a certain level of lock-in. If you decide to switch to a different ORM in the future, you may need to refactor your codebase and adapt to the new ORM’s paradigms and syntax.
Another consideration is the learning curve if you are not familiar with SQL or you’re used to other existing ORMs like TypeORM or Prisma (which provides an abstraction layer that can make it easier to work with in some ways). This may create a steeper learning curve for developers who are accustomed to other ORMs.
Some developers have also found Drizzle’s documentation somewhat confusing and frustrating to use. However, if you can get around these difficulties, Drizzle offers many advantages that make it worth exploring.
Drizzle ORM’s features aim to improve your productivity and enable efficient database interactions. Here are some key features to be aware of when working with Drizzle:
To see Drizzle in action, we’ll build a simple web API using Drizzle ORM and MySQL — specifically, a user CRUD API. We’ll demonstrate generating a backend using Drizzle, Prisma, and TypeORM, comparing the three in terms of flexibility and handling complex queries.
Before you start, you’ll need to have Node.js ≥v16.18.0, npm ≥v8.19.2, and Yarn ≥v1.22.19 installed on your development machine:
npm i -g yarn
You’ll also need to install MySQL. You can either install it directly by using a MySQL binary or by using Docker. We won’t cover installing and setting up MySQL because it’s out of the scope of this article. Instead, I recommend following the steps in this article.
The full code for this tutorial is available on GitHub, so you can easily follow along and refer back to it as needed. Let’s get started!
First, let’s install Drizzle. For our example, we’ll use the MySQL database, but Drizzle also supports other databases. Install the specific Drizzle driver for MySQL with the code below:
yarn add drizzle-orm mysql2
We’ll need two main packages to connect and query the database using Drizzle. The mysql2 package is a popular Node.js package to connect and interact with MySQL. Drizzle uses its pool connections to query and mutate data:
yarn add -D drizzle-kit
We’ll also use Drizzle Kit, which will help to either generate or update our migrations. However, it doesn’t help us apply them. You’ll either have to apply the migrations yourself or use a different tool.
In this article, we’ll provide a step-by-step guide to building a web API from scratch using Drizzle ORM, focusing mainly on the experience of implementing flexible queries.
To create data models with Drizzle, you need to define the tables in your database as TypeScript objects. The data models are different from regular TypeScript objects; they rely heavily on a function-based syntax and Drizzle annotations to define the tables’ columns, relationships, and other metadata.
The example below shows a simple user table inside a schema file based on the Drizzle syntax:
import { mysqlTable, serial, uniqueIndex, varchar, } from 'drizzle-orm/mysql-core'; export const users = mysqlTable('users', { id: serial('id').primaryKey(), name: varchar('name', { length: 256 }), email: varchar('email', { length: 256 }), }, (users) => ({ emailIndex: uniqueIndex('email_idx').on(users.email), }));
The code above is very similar to defining tables in SQL, but the table structure is defined as a TypeScript object, thereby providing better type safety and code completion. Using Drizzle’s annotations and functions, you can define primary keys, indexes, foreign keys, and more.
Another thing to notice is the import of functions from the drizzle-orm/mysql-core
package, which provides the necessary tools to define MySQL-specific data models. If you use a different database system, you’ll need to import a different route.
With the Drizzle Kit npm package, Drizzle can write the SQL statements needed to migrate your database by generating migration files for you. However, it’s worth noting that Drizzle doesn’t provide a tool for applying these migrations, so you’ll need to apply them using a separate tool.
But, before you can generate your migrations, you need to create a Drizzle config file for your project. In the root directory of your project, create a file called drizzle.config.json
:
{ "out": "./src/db/migrations", "schema": "./src/db/schema.ts", "breakpoints": true }
As shown in the example above, you need to specify the output directory for your migration files along with pointers to your schema file. You must also specify whether or not breakpoints should be enabled. Enabling breakpoints will ensure that your generated migration file won’t execute multiple DDL statements in one transaction, which isn’t supported by all database drivers.
For example, MySQL doesn’t support it, and therefore, you should enable breakpoints in our example. Once you’ve added the config file, you can use Drizzle Kit to generate or update your migrations by running the following command:
drizzle-kit generate:mysql
This will generate migration files based on your schema and output them to the specified directory. Also, because you’re using MySQL specifically, you need to use the generate:mysql
command. To update your migrations with any changes to your schema, simply run the same command again, and Drizzle will generate new migration files that incorporate those changes.
After you’ve written the model and the migrations for your database, next, we’ll write the Express handlers. Assuming that you’ve also run your migrations, Drizzle will be able to connect to your database and query your table based on the schema you defined.
Create a new file to define the pool connection using the mysql2 package and the credentials for your database. Then, initiate a new Drizzle instance using that connection as follows:
import "dotenv/config"; import { drizzle } from 'drizzle-orm/mysql2'; import mysql from 'mysql2/promise'; // Create the connection const poolConnection = mysql.createPool(process.env.DATABASE_URL as string); export const db = drizzle(poolConnection);
For simplicity, the db
constant in the code above contains the Drizzle instance in the form of a singleton, which was created using the previously-established connection pool. However, making singletons this way isn’t the best practice because a pool will already keep a cache of the connection for reuse. Instead, it’s better to use a pool and let it manage the connections.
Next, define the Express app with the necessary middleware as follows:
import express from 'express'; const app = express(); const port = process.env.PORT || 3000; app.use(express.json()); // Insert routes here... app.listen(port, () => { console.log(`Server is listening on port ${port}.`); });
We haven’t defined the routes yet. We’re creating a simple CRUD system, so we’ll need routes for creating, reading, updating, and deleting records. Next, you can make a simple GET
request to retrieve all the users.
On the top of the file, we’ll add a couple more imports that will help to access the database and perform queries:
import { db } from './db'; import { users as userSchema } from './db/schema'; import { eq } from 'drizzle-orm'; Then, define the GET routes: app.get('/users', async (req, res) => { const users = await db.select().from(userSchema); res.json(users); }); app.get('/users/:id', async (req, res) => { const { id } = req.params; const user = await db.select().from(userSchema).where(eq(userSchema.id, Number(id))); res.json(user); });
These routes allow you to retrieve all the users as well as specific users by their ID from the database using Drizzle’s query-building capabilities. As you can see, Drizzle’s query builder syntax is very similar to that of SQL. You can select columns using the select()
method and specify the table using the from()
method.
For now, we’ve only performed the read operations. Next, we’ll add the routes for creating, updating, and deleting records:
app.post('/users', async (req, res) => { const { name, email } = req.body; const user = await db.insert(userSchema).values([{ name, email }]); res.json(user); }); app.put('/users/:id', async (req, res) => { const { id } = req.params; const { name, email } = req.body; const user = await db.update(userSchema).set({ name, email }).where(eq(userSchema.id, Number(id))); res.json(user); }); app.delete('/users/:id', async (req, res) => { const { id } = req.params; const user = await db.delete(userSchema).where(eq(userSchema.id, Number(id))); res.json(user); });
Now, we understand how to do basic read operations, so creating, updating, and deleting operations aren’t that far off.
In this example, we defined the routes for a simple CRUD system using Drizzle ORM to access a database. Overall, Drizzle’s query-building capabilities make it relatively simple to perform database operations that are concise and readable.
It’s important to follow best practices when handling user input, like validating and sanitizing the data before sending it to the database. It’s also recommended to manage database connections properly and securely because improper connection management can lead to security vulnerabilities or performance issues.
Drizzle has been successfully used for many different use cases in the past. As a versatile tool, here are some of the ways you can use Drizzle:
Drizzle is certainly a fresh and promising ORM that offers a relatively lightweight and flexible query builder, making it a good choice for simple to moderately complex database operations. It provides a good balance between simplicity and customization without sacrificing performance.
Instead of being the all-in-one solution for every kind of database and application, Drizzle’s focus is on providing a solid foundation for easily building database-driven apps. Drizzle functions more like a query-builder than anything else. At the time of writing, the biggest feature it’s missing is support for applying database migrations.
Despite both being ORMs, Drizzle and Prisma take different approaches to database access. Drizzle focuses on providing a lightweight and flexible query builder, while Prisma is more opinionated and provides a stronger level of abstraction, allowing for easier database migrations and schema management.
Drizzle offers more flexibility when it comes to querying, and it allows you to take a more direct approach to database access. Using Drizzle is similar to using SQL; it’s not hard to perform complex JOINs with Drizzle, but it requires more manual coding than using Prisma.
Another problem occurs when using Prisma in serverless environments where you need to keep server startup time to a minimum. Prisma’s initial setup can take some time because it uses a Rust binary to maintain connections to the database. This can lead to performance issues in serverless environments where resources are limited.
On the other hand, Drizzle is perfect for serverless environments because it is written in TypeScript and doesn’t need to load a Rust binary. You can use regular connection pools in JavaScript and TypeScript. Although it may still require some setup time to initiate, it won’t be as heavy as Prisma’s setup process.
TypeORM is one of the most popular JavaScript ORMs. While Drizzle focuses more on syntax, TypeORM focuses more on type annotations and schema management. Both Prisma and TypeORM have the advantage of being able to create and run migrations, which, at the time of writing, Drizzle hasn’t implemented yet.
TypeORM is more lightweight than Prisma, but Drizzle still offers a lighter alternative in terms of query building and database access. One advantage that TypeORM has over Drizzle is that it doesn’t require you to install different drivers for different databases. Like Prisma, TypeORM gives you a proper abstraction layer, which Drizzle doesn’t yet provide fully.
Here’s a comparison table to help you understand the differences between Drizzle, TypeORM, and Prisma at a glance:
Comparison point | Drizzle | Prisma | TypeORM |
---|---|---|---|
Abstraction level | Lightweight query builder with a focus on simplicity and flexibility | High-level ORM with a strong focus on developer experience and productivity | Full-featured ORM with a higher level of abstraction |
Features | Query builder, type-safe database interactions, raw query support, transactions, eager/lazy loading | Schema definition, type-safe queries, migrations, introspection, query engine, middleware, raw database access | Entity manager, query builder, migrations, transactions, events, listeners, decorators |
Type safety | Strong type safety with TypeScript integration, leveraging TypeScript’s static typing capabilities | Strong type safety with automatically generated TypeScript types based on the database schema | Provides type safety through decorators and entity classes, but may require additional configuration |
Performance | Lightweight and optimized for performance, minimal overhead | Good performance with optimized query execution, but may have some startup overhead | Good performance, but may have some overhead due to extensive features |
Documentation | Comprehensive documentation with examples and usage guides | Detailed documentation, guides, tutorials, and API reference with examples | Extensive documentation, guides, and API reference |
Community | Growing community, active development, and increasing adoption | Large and active community, extensive ecosystem, and strong industry adoption | Large and active community, extensive ecosystem, and widespread adoption |
You can also check out this code repository for a code comparison between Drizzle, Prisma, and TypeORM in terms of code syntax and ease of use within a small project. Although this isn’t a comprehensive comparison, it should give you an idea of how each ORM works and which one might be best suited for your project.
There are a lot of ORM tools available to choose from, and the number of options is increasing all the time. For TypeScript developers, Prisma has been the go-to ORM for a long time because many developers recognize its strong focus on type safety and ease of use. However, Drizzle is a viable alternative to type safe ORMs like Prisma.
Drizzle is still relatively new, so there’s much to be desired. However, it’s already usable in a production environment for several relational databases. And, if you’re familiar with any notable databases like MySQL or Postgres, you can use Drizzle confidently.
Drizzle offers a lightweight alternative to other ORMs like Prisma and TypeORM. Although it is still relatively new and lacks some advanced features, like migrations, it is already usable in a production environment for several relational databases.
If you’re a TypeScript developer looking for a lightweight ORM with a focus on query building and ease of use, Drizzle could be the perfect option for you.
I hope you enjoyed this article, and be sure to leave a comment if you have any questions.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.