Back to Blog

Goodbye Prisma, Hello Drizzle

Jay FreemanJay Freeman

As I begin to write this, I suddenly start to feel a little sad.

This is the first Electron app that we've ever built, and a Prisma error when building and launching DB Pro on Mac has forced us to rethink our choices when it comes to an ORM.

I can't help but feel nostalgic, sad, but also slightly excited.

Nostalgic because we have been using Prisma for almost three years and are still very big fans of them and the tool.

Sad because as I said, we like Prisma. However, getting Prisma to play nicely in Electron was proving to be a fiddly, difficult task. And the older I get, the more I just want things to work.

Slightly excited because as developers we love trying new tools, especially in the web and TypeScript space. Therefore, I'm excited to give Drizzle a try.

Drizzle is a tool I remember hearing a lot of noise about roughly five years ago, and then not really anything else since. Even so, it's a new tool and so I'm excited to give it a try.

Why Electron and Prisma Don’t Get Along

I think I'd still be using Prisma in our Electron app, DB Pro, if the app built fine the first try. Unfortunately, we're seeing this error:

Error: Cannot find module '.prisma/client/default' Require stack: - /Applications/DB Pro.app/Contents/Resources/app.asar/node_modules/@prisma/client/default.js - /Applications/DB Pro.app/Contents/Resources/app.asar/out/main/index.js - at Module._resolveFilename (node:internal/modules/cjs/loader:1372:15) at s._resolveFilename (node:electron/js2c/browser_init:2:129714) at defaultResolveImpl (node:internal/modules/cjs/loader:1028:19) at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1033:22) at Module._load (node:internal/modules/cjs/loader:1182:37) at c._load (node:electron/js2c/node_init:2:17993) at TracingChannel.traceSync (node:diagnostics_channel:322:14) at wrapModuleLoad (node:internal/modules/cjs/loader:242:24) at Module.require (node:internal/modules/cjs/loader:1452:12) at require (node:internal/modules/helpers:135:16)

Apparently this is a very common Prisma + Electron packaging issue. The generated Prisma client and its native engine files basically didn't make it into the packaged app where Prisma can be loaded from.

A little backstory as to how Prisma works under the hood:

You write a schema.prisma file. I love this file. The syntax is wonderfully clear, and it's just such a great single source of truth for your data schema.

Prisma then generates a client from that schema using prisma generate.

This creates a .prisma/client folder at runtime.

Then, to actually function, Prisma uses a native engine file, historically a Rust binary.

The engine starts, and Prisma communicates to it via RPC. So JavaScript code runs, speaks to the Prisma Client and then that engine executes SQL against the database, which returns the results back to JavaScript.

Size Matters

This worked fantastically on every web product we've ever built. Until we switched to building this Electron app.

Also, and I didn't really know this until researching this error, Prisma generates a huge JS client in node_modules/@prisma/client/.prisma/client. Like, 20-50MB depending on the platform. In Electron it could be 20-50 MB once you include the client + engines.

Guess how big the core Drizzle client is.

30 KB gzipped.

Yep. You read that right.

The build size for DB Pro dropped from 523 MB to ~300 MB. That's huge when you're considering installing an app on your machine. Also, some users are super picky about the size of the app they're installing.

I don't know. Doing some deeper research into how Prisma works under the hood is making me think that long-term, it's not the right decision to run an "engine" as a middle-person to communicate between JS and your database. That feels super heavy for something that is remarkably simple.

Prisma vs Drizzle: Side-by-Side

As I said before, I love how verbose the Prisma schema file is. It's so clear.

Let's take a look at a basic schema file for a users table.

// schema.prisma
datasource db {
  provider = "sqlite"
  url      = "file:./dbpro.db"
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
}

And let's see the same schema in Drizzle

TS
// src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

// Define a "users" table
export const users = sqliteTable("users", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  email: text("email").notNull().unique(),
  name: text("name"),
  createdAt: integer("created_at").default(() => Date.now()),
});

I just prefer Prisma's syntax. I don't always want everything to be defined in JavaScript/TypeScript. It's just not as neat to my eyes.

The syntax for actually using that table in Drizzle is a little better though. Let's see it first in Prisma:

TS
// src/db/client.ts
import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient();

// Insert
await prisma.user.create({
  data: { email: "alice@example.com", name: "Alice" },
});

// Query
const allUsers = await prisma.user.findMany();
console.log(allUsers);

Man, look at that. It's so neat. Perhaps my eyes are just used to looking at the structure and syntax of Prisma code, but that to be is incredibly clear.

And for Drizzle:

TS
// src/db/client.ts
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { users } from "./schema";

const sqlite = new Database("dbpro.db");
export const db = drizzle(sqlite);

// Insert
await db.insert(users).values({ email: "alice@example.com", name: "Alice" });

// Query
const allUsers = await db.select().from(users);
console.log(allUsers);

Not bad.

Of course, this is all my opinion, so take it or leave it kind of thing. I don't know about you guys but I prefer Prisma's schema and usage syntax. It's just clearer. Maybe it's because I've been using it longer and Drizzle is still so new to me though.

Ask me again in a year.

The Rabbit (Drizzle) and the Tortoise (Prisma)

Drizzle have a neat little Benchmarks page on their site. It seems to be running a connection to a database using Bun and Drizzle, and Bun and Prisma, and comparing the timings of both.

Prisma's average latency is ~450ms Drizzle's average latency is ~30ms.

That's a crazy difference in speed. And speed matters if you're storing hundreds of logs per second into a SQLite DB, as we are in DB Pro.

The Future is Drizzly

Yes I'm sad to say goodbye to Prisma. Is it for the best though? Oh, absolutely. The goal is for DB Pro to be a light-weight but performant DB client and for that to happen we need to use the best tools at our disposal.

Maybe the Prisma team can change my mind one day, but until then, I'm packing my umbrella and stepping outside.

It's drizzling ☔️