[{"data":1,"prerenderedAt":28},["ShallowReactive",2],{"$feGoDBWJs-cUZJu8J45sKlCo8193X55_aIiKtNNLhE4k":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"draft":12,"readingMinutes":13,"html":14,"related":15},"drizzle-vs-prisma","Drizzle vs Prisma in Late 2026: Which ORM I'd Start With","Drizzle vs Prisma in October 2026: Prisma 8 is a release candidate that npm already installs, Drizzle v1 is still an RC. What that means for a new TypeScript project.","2026-10-25",[9,10,11],"typescript","node","databases",false,5,"\u003Cp>If you start a TypeScript project this week, my pick is Drizzle, unless your team already knows Prisma well. In that case it&#39;s Prisma 7, pinned, and not Prisma 8. The reason is less about query syntax than about where both projects are right now: each one is in the middle of a major version, and Prisma&#39;s has a sharp edge you can cut yourself on just by running \u003Ccode>npm install\u003C/code>.\u003C/p>\n\u003Cp>Everything below was checked on October 25, 2026, against npm and the two projects&#39; own docs.\u003C/p>\n\u003Ch2>What npm actually gives you today\u003C/h2>\n\u003Cp>This is the part most comparison posts are missing, because it only happened in the last few weeks. Prisma&#39;s \u003Ca href=\"https://www.prisma.io/changelog/2026-10-02\">October 2 changelog\u003C/a> says it plainly: Prisma ORM 8 &quot;is already what \u003Ccode>prisma@latest\u003C/code> installs, while Prisma ORM 7 is \u003Ccode>prisma@prev\u003C/code>.&quot; But only for the CLI package. The client package still points at 7.\u003C/p>\n\u003Cimg src=\"/blog/drizzle-vs-prisma/npm-dist-tags-prisma-drizzle.webp\" alt=\"Terminal output of npm view on October 25, 2026: prisma latest is 8.0.0-rc.27, @prisma/client latest is 7.10.1, drizzle-orm latest is 0.45.4 with 1.0.0-rc.5 on the rc tag\" width=\"1600\" height=\"900\" loading=\"lazy\">\u003Cp>So a fresh \u003Ccode>npm i -D prisma\u003C/code> and \u003Ccode>npm i @prisma/client\u003C/code> gets you a version 8 release candidate CLI next to a version 7 client. Prisma&#39;s \u003Ca href=\"https://www.prisma.io/docs/orm/release-status\">release status page\u003C/a> says the version 8 CLI &quot;does not read \u003Ccode>schema.prisma\u003C/code>&quot; (it has its own schema format, which Prisma calls the contract), though since October 2 it can read an existing Prisma 7 Postgres schema as it is. It also needs Node 22.18+ or 24.11+ (24.0 to 24.10 don&#39;t work) and TypeScript 5.9+. If you followed a tutorial from last spring, none of that is in it.\u003C/p>\n\u003Cp>The fix is one line in \u003Ccode>package.json\u003C/code>, and Prisma recommends it themselves until 8 is final:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-json\">&quot;prisma&quot;: &quot;^7&quot;,\n&quot;@prisma/client&quot;: &quot;^7&quot;\n\u003C/code>\u003C/pre>\n\u003Cp>General availability for 8 is &quot;expected in October 2026,&quot; and version 7 gets bug fixes and security updates for 18 months after that. So 7 is a safe place to sit. I wouldn&#39;t build anything real on the 8 RC this month. rc.27 shipped on October 23, which tells you how fast it&#39;s still moving.\u003C/p>\n\u003Cp>Drizzle is mid-upgrade too, but in the opposite way. \u003Ccode>latest\u003C/code> is still 0.45.4 (October 8), and v1 lives on the \u003Ccode>rc\u003C/code> tag (1.0.0-rc.5, October 21). You only get v1 if you ask for it with \u003Ccode>npm i drizzle-orm@rc drizzle-kit@rc\u003C/code>. Nothing surprises you.\u003C/p>\n\u003Ch2>How they feel to write\u003C/h2>\n\u003Cp>Drizzle schemas are TypeScript. There is no generate step, no separate file format, and the query builder reads like the SQL it produces:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-ts\">// schema.ts\nimport { pgTable, serial, text, integer, timestamp } from &#39;drizzle-orm/pg-core&#39;;\n\nexport const users = pgTable(&#39;users&#39;, {\n  id: serial(&#39;id&#39;).primaryKey(),\n  email: text(&#39;email&#39;).notNull().unique(),\n});\n\nexport const orders = pgTable(&#39;orders&#39;, {\n  id: serial(&#39;id&#39;).primaryKey(),\n  userId: integer(&#39;user_id&#39;).notNull().references(() =&gt; users.id),\n  totalCents: integer(&#39;total_cents&#39;).notNull(),\n  createdAt: timestamp(&#39;created_at&#39;).defaultNow(),\n});\n\u003C/code>\u003C/pre>\n\u003Cpre>\u003Ccode class=\"language-ts\">import { drizzle } from &#39;drizzle-orm/node-postgres&#39;;\nimport { eq, sum } from &#39;drizzle-orm&#39;;\nimport { users, orders } from &#39;./schema&#39;;\n\nconst db = drizzle(process.env.DATABASE_URL!);\n\nconst spend = await db\n  .select({ email: users.email, spent: sum(orders.totalCents) })\n  .from(users)\n  .leftJoin(orders, eq(orders.userId, users.id))\n  .groupBy(users.email);\n\u003C/code>\u003C/pre>\n\u003Cp>If you know SQL, you already know Drizzle. That&#39;s my main reason for liking it. When a query is slow, I can read it and see the join, and the \u003Ccode>EXPLAIN\u003C/code> output matches what I wrote.\u003C/p>\n\u003Cp>Prisma 7 is schema-first. You write \u003Ccode>schema.prisma\u003C/code>, run \u003Ccode>prisma generate\u003C/code>, and get a client with the nicest autocomplete in the TypeScript world. Version 7 changed a lot about setup, though, so older answers on Stack Overflow will mislead you. Per the \u003Ca href=\"https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-7\">Prisma 7 upgrade guide\u003C/a>: the generator is now \u003Ccode>prisma-client\u003C/code> with a required \u003Ccode>output\u003C/code> path, the client needs a driver adapter for every database, the database URL moved into \u003Ccode>prisma.config.ts\u003C/code>, the package is ESM only, and \u003Ccode>.env\u003C/code> is no longer loaded for you.\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-prisma\">generator client {\n  provider = &quot;prisma-client&quot;\n  output   = &quot;../src/generated/prisma&quot;\n}\n\ndatasource db {\n  provider = &quot;postgresql&quot;\n}\n\nmodel User {\n  id     Int     @id @default(autoincrement())\n  email  String  @unique\n  orders Order[]\n}\n\nmodel Order {\n  id         Int      @id @default(autoincrement())\n  userId     Int\n  user       User     @relation(fields: [userId], references: [id])\n  totalCents Int\n  createdAt  DateTime @default(now())\n}\n\u003C/code>\u003C/pre>\n\u003Cpre>\u003Ccode class=\"language-ts\">import { PrismaClient } from &#39;./generated/prisma/client&#39;;\nimport { PrismaPg } from &#39;@prisma/adapter-pg&#39;;\n\nconst prisma = new PrismaClient({\n  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),\n});\n\nconst spend = await prisma.order.groupBy({\n  by: [&#39;userId&#39;],\n  _sum: { totalCents: true },\n});\n\u003C/code>\u003C/pre>\n\u003Cp>Fine for that query. The trouble starts when you need something \u003Ccode>groupBy\u003C/code> can&#39;t express, like the email in the same result. Then you drop to \u003Ccode>$queryRaw\u003C/code>, and now part of your data layer is typed and part is strings. With Drizzle that query was just a join.\u003C/p>\n\u003Ch2>Relations and migrations\u003C/h2>\n\u003Cp>Prisma&#39;s relation model is still its best feature. You declare \u003Ccode>orders Order[]\u003C/code> and \u003Ccode>include: { orders: true }\u003C/code> just works, with nested writes on top. Drizzle&#39;s equivalent is its relational query API, and v1 replaces it: the old \u003Ccode>relations()\u003C/code> helper is gone, and you declare everything once with \u003Ccode>defineRelations()\u003C/code> (\u003Ca href=\"https://orm.drizzle.team/docs/relations-v1-v2\">Drizzle&#39;s v2 relations guide\u003C/a>). If you start on 0.45 and use relational queries, budget an afternoon to move them later.\u003C/p>\n\u003Cp>Migrations differ in style. \u003Ccode>prisma migrate dev\u003C/code> diffs your schema and writes SQL, and it&#39;s the most guided workflow either tool has. \u003Ccode>drizzle-kit generate\u003C/code> does the same from your TypeScript schema. Upgrading Drizzle to v1 means running \u003Ccode>drizzle-kit up\u003C/code> once, which removes \u003Ccode>journal.json\u003C/code> and puts each migration&#39;s SQL and snapshot in its own folder (\u003Ca href=\"https://orm.drizzle.team/docs/upgrade-v1\">Drizzle&#39;s v1 upgrade page\u003C/a>). Commit before you run it.\u003C/p>\n\u003Ch2>Size and where it runs\u003C/h2>\n\u003Cp>npm reports the unpacked size of \u003Ccode>@prisma/client\u003C/code> 7.10.1 at about 74 MB and \u003Ccode>drizzle-orm\u003C/code> 0.45.4 at about 10.5 MB. That isn&#39;t what ends up in your bundle, but it hints at how much more machinery Prisma carries. Prisma 7&#39;s Rust-free client closed a lot of the old gap on serverless and edge runtimes, which was the main complaint for years. Still, Drizzle has always been the thinner layer, and on Cloudflare Workers or a Lambda with tight cold starts I&#39;d rather have less in the way.\u003C/p>\n\u003Ch2>So which one\u003C/h2>\n\u003Cul>\n\u003Cli>\u003Cstrong>New project, you&#39;re comfortable with SQL:\u003C/strong> Drizzle 0.45 now, move to v1 when it&#39;s out of RC.\u003C/li>\n\u003Cli>\u003Cstrong>Team that already ships Prisma:\u003C/strong> stay on 7 and pin it. Read the version 8 migration pages after GA, not before.\u003C/li>\n\u003Cli>\u003Cstrong>Lots of nested relational reads, little custom SQL:\u003C/strong> Prisma 7 is still more pleasant there.\u003C/li>\n\u003Cli>\u003Cstrong>Anything with reporting queries or odd joins:\u003C/strong> Drizzle, because you&#39;ll write them anyway.\u003C/li>\n\u003C/ul>\n\u003Cp>What I wouldn&#39;t do is copy a &quot;getting started&quot; snippet from either project&#39;s docs without checking which major version it targets. Both sites have v1/v7 and RC pages side by side right now, and they don&#39;t always say which one you&#39;re reading.\u003C/p>\n\u003Cp>If you&#39;re stuck between versions on a real codebase, or an ORM upgrade broke your deploy, \u003Ca href=\"https://calendly.com/director-norsky/meeting\">book a free call\u003C/a> and I&#39;ll take a look.\u003C/p>\n",[16,20,24],{"slug":17,"title":18,"description":19},"s3-presigned-url","S3 Presigned URLs: Uploads, Expiry and the Errors You'll Hit","How S3 presigned URLs work in 2026: the real expiry limits (7 days, 12 hours, or whenever your role credentials die), browser uploads with Node, and the 403s that come with them.",{"slug":21,"title":22,"description":23},"puppeteer-vs-playwright","Puppeteer vs Playwright: Which One I'd Pick in 2026","Puppeteer vs Playwright, checked October 2026: browsers, waiting, the test runner, PDFs and installs, and which one fits testing, automation or a PDF service.",{"slug":25,"title":26,"description":27},"bullmq","BullMQ in Production: The Settings That Bite","BullMQ works on the first try in development. These are the Redis and job settings that decide whether it still works in production, checked October 2026.",1792887408534]