Blog

My Journey as a Web Developer

Drizzle ORM - Set up and Comparison With Prisma

03/05/2024

author avatar

Intro

Drizzle ORM is a TypeScript ORM. Prisma is also type safe and it is very easy to use. Prisma has extensive documentation that is beautifully designed and clearly laid out. So why, then, would we consider Drizzle?

One of my favourite features of Drizzle and the thing that captured my interest was that it very SQL-like. Their motto is 'If you know SQL, you know Drizzle". Now, I know SQL, with a focus on PostgreSQL, but I don't get to use it too often, mainly choosing Prisma for my projects. With Drizzle though, I can get back close to SQL but still have the ease of use an ORM.

Setup

In this post, we're not going to do any project or build anything in particular. This is solely focused on setting it up and using the Drizzle API. That said, we will have to have a database setup and I will be using Supabase. It's simple and easy to set up, so I won't cover that here.

We will need the following packages.

npm i drizzle-kit drizzle-orm postgres tsx dotenv

Create your Supabase database and make sure to put the connecting string as a DATABASE_URl in your file .env file.

Now we will need create a config file called drizzle.config.ts in the route of our project.

drizzle.config.ts

import type { Config } from "drizzle-kit";
import "dotenv/config";

export default {
  schema: "./src/lib/drizzle/schema/*",
  out: "./src/lib/drizzle/migrations",
  driver: "pg",
  verbose: true,
  dbCredentials: {
    connectionString: process.env.DATABASE_URL as string,
  },
} satisfies Config;

Schema: We will have a multiple schema files, so we add an asterisk at the end of our schema string.

Out: The out line tells Drizzle where we want our migrations files to go.

Driver: Options 'pg' | 'mysql2' | 'better-sqlite' | 'libsql' | 'turso' | 'd1'. We are using PostgreSQL, so we use 'pg'.

Verbose: For longer logs on push commands.

Schema

Let's create a very simple schema for our users.

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: text("name"),
});

Drizzle vs Prisma Schemas and Models

Prisma models are directly comparable to Drizzle Schemas. One advantage of Drizzle is we are writing these in typescript, and it is just a regular JavaScript function. However, I enjoy the structure of Prisma models much better than this. Let's take a look at a basic user setup for next auth to compare.

Prisma model for a basic next auth user.

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

Drizzle model for a basic next auth user.

export const users = pgTable("user", {
 id: text("id").notNull().primaryKey(),
 name: text("name"),
 email: text("email").notNull(),
 emailVerified: timestamp("emailVerified", { mode: "date" }),
 image: text("image"),
})

export const accounts = pgTable(
"account",
{
  userId: text("userId")
    .notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  type: text("type").$type<AdapterAccount["type"]>().notNull(),
  provider: text("provider").notNull(),
  providerAccountId: text("providerAccountId").notNull(),
  refresh_token: text("refresh_token"),
  access_token: text("access_token"),
  expires_at: integer("expires_at"),
  token_type: text("token_type"),
  scope: text("scope"),
   id_token: text("id_token"),
  session_state: text("session_state"),
},
(account) => ({
  compoundKey: primaryKey({ columns: [account.provider, account.providerAccountId] }),
})
)

I find the Drizzle schema more difficult to read than the Prisma model, just mainly due to it being easier on the eyes. That said, it's not so unpleasant as to put me off using it altogether. There's a lot of good stuff to come, so hopefully it hasn't put you off either.

Generating Migrate Files

Now we need to generate the migration files. To do this we will configure a script in our package json file.

"generate": "drizzle-kit generate:pg"
npm run dev
and you should see some files added to your specified out folder. Now we need to run these generated migrations files and apply them to our database.

Migrating Changes

We will configure another script in our package json file.

"migrate": "tsx -r dotenv/config src/lib/drizzle/migrate.ts",

Breaking the script down, we are using the package tsx to run our Typescript file, telling it to require the dotenv/config package and run the file migrate.ts. Now we will need to go and create that file before running it, naturally.

migrate.ts

import { db, connection } from "./index";
import { migrate } from "drizzle-orm/postgres-js/migrator";

const runDrizzleMigrate = async () => {
  try {
    await migrate(db, { migrationsFolder: "src/lib/drizzle/migrations" });
    console.log("Migrations completed successfully");
  } catch (error) {
    console.log(error);  
  } finally {
    await connection.end();
  }
};
runDrizzleMigrate();

Here, we are using the provided migrate function from Drizzle. We drop our connection otherwise the script will continue to hang.

npm run migrate
Viola, hopefully you see 'Migrations completed successfully'.

Inserting a User

// Drizzle
const user = await db.insert(users).values({ name: "John" }).returning();

// Prisma
const user = await db.user.create({data: {name: 'John'}})

Updating a User

// Drizzle
await db.update(users)
  .set({ name: 'Jane' })
  .where(eq(users.name, 'John'));

// Prisma 
await db.user.update({
  where: {
    name: 'John,
  },
  data: {
    name: 'Jane',
  },
});

Deleting a User

// Drizzle
await db.delete(users).where(eq(users.name, 'Dan'));

// Prisma
await db.user.delete({where: {name: 'John'}})

Reading a user

Drizzle offers to ways to query users as mentioned earlier. They have relational (prisma-like) and SQL like.

// Drizzle SQL-like
const users = await db.select().from(users); 

// Drizzle relational
const users = await db.query.users.findMany()

// Prisma 
const users = await db.user.findMany()

What about Relations and more complex operations?

Above are the simple CRUD operations with no filtering, sorting, limiting, or relations. This will be covered in the next post, so look out for part 2.

Summary

Drizzle offers a Typescript ORM, so there is no new syntax to even learn when interacting with your database. It does however, offer the opportunity to remove a little of the disguise that ORMs provide whilst still retaining the ability to use an easy to understand and more logical API with its relational querying.

← Blog Home