mrkeyoor.com_
Sat 19 Sept 06:41 UTC
npmDataupdated 19 Sept 2026

drizzle-orm review

Our install of drizzle-orm 0.45.2 contained a large set of typed entry points for PostgreSQL, MySQL, SQLite, and hosted variants, yet no required runtime dependency. You declare tables in TypeScript, connect through a separately installed database driver, and compose SQL-shaped queries whose result types follow the selected columns and joins. Drizzle Kit handles schema diffs and migrations outside the runtime package. The current stable release is a security fix: 0.45.2 corrects escaping in sql.identifier() and sql.as() that could permit SQL injection. The 1.0 line is still on release-candidate tags, so examples written for its newer relational query API may not match the stable package.

Verdict

Drizzle 0.45.2 is a good fit for TypeScript teams that want SQL to stay visible and need a driver-specific path into serverless databases. Install the current patch, budget for the separate driver and migration tool, and avoid 1.0 examples until you deliberately move to that release line.

We installed it

Lab card: what happened when we installed drizzle-ormScreenshot of drizzle-orm documentation
Install✓ · 17.9s1 package on disk · 17 MB
ImportESM import works · require() works · ESM package with exports map
Browser8.5 KBgzipped (27.6 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does drizzle-orm install cleanly?

Yes. In a fresh container with an empty cache, npm install drizzle-orm finished in 18 seconds, leaving 1 package and 17 MB on disk. npm audit reported no known vulnerabilities.

How much does drizzle-orm add to a browser bundle?

8.5 KB gzipped (27.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does drizzle-orm work with both ESM and CommonJS?

Yes. Both import 'drizzle-orm' and require('drizzle-orm') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does drizzle-orm include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

drizzle-orm or prisma: which should you use?

prisma: Choose it when a schema DSL, generated client, nested relation API, and broader onboarding material matter more than a thin SQL-shaped layer. Drizzle 0.45.2 is a good fit for TypeScript teams that want SQL to stay visible and need a driver-specific path into serverless databases.

When should you not use drizzle-orm?

Your team wants an ORM to hide joins, indexes, dialect rules, and transaction details. Drizzle's query builder intentionally mirrors SQL concepts

API stability3/5The stable query builder, table definitions, operators, transactions, and driver entry points are usable across the 0.45 line, but the project has not declared a stable 1.0 release. npm simultaneously exposes 0.45.2 as latest and 1.0.0 release candidates on separate tags. The relational query API is an area of version skew, and 0.45.2 itself changes escaping behavior in two raw-SQL helpers for a necessary security correction.
Docs4/5The official site has an overview plus separate start guides by dialect, provider, and driver, then reference sections for schemas, selects, inserts, updates, deletes, relations, transactions, migrations, and raw SQL. Concrete provider pages reduce guesswork for edge databases. The deduction comes from stable 0.x pages, 1.0 release-candidate material, and third-party tutorials coexisting with similar search titles but different APIs.
Maintenance4/5The repository was pushed on 19 August 2026, GitHub showed 35,566 stars, and release work continues across the stable and 1.0 candidate lines. Version 0.45.2 documents the affected escaping functions and the SQL injection class it fixes. The large queue is the concern: GitHub's open count was 1,981 issues and pull requests, which makes response time for a niche driver combination harder to predict.
Ecosystem5/5npm recorded 19,684,948 downloads for 17 through 23 August 2026. The peer list covers pg, postgres, mysql2, better-sqlite3, sql.js, libSQL, Neon, PlanetScale, PGlite, Expo SQLite, Cloudflare Workers, AWS RDS Data API, and other hosted variants. Drizzle Kit and Studio fill the migration and inspection roles, while integrations with Kysely, Prisma, OpenTelemetry, and multiple runtimes broaden where the same schema can be used.

Use it if

  • Your TypeScript team already understands SQL and wants compile-time row shapes without an generated query client or resident engine
  • The same schema layer must work with a small serverless driver such as Neon, libSQL, Cloudflare D1, PGlite, or PlanetScale
  • You need control over selected columns, joins, conflict clauses, transactions, and raw SQL fragments while preserving parameter binding
  • Schema-as-code and reviewable SQL migration files fit your deployment process better than a separate declarative schema language
Skip it if

Setup reality

We installed drizzle-orm 0.45.2 in a fresh Node 22 Bookworm container with no cache. npm completed in 17.9 seconds and left one package using 17 MB on disk. The published package is 16,984 KB unpacked, with zero direct dependencies and 28 peer dependencies. npm audit found zero known vulnerabilities. It declares ESM and an exports map, while both require() and ESM import worked. TypeScript declarations are bundled.

That single-package result is only the ORM layer. Pick one matching entry point and install its driver: drizzle-orm/node-postgres with pg, drizzle-orm/postgres-js with postgres, drizzle-orm/mysql2 with mysql2, or a service-specific adapter. Most of the 28 peers are optional choices rather than a list to install together. Passing your schema into drizzle() is required for db.query table access. Connection strings, TLS, pools, and serverless limits still belong to the selected driver.

Migration work adds drizzle-kit and drizzle.config.ts. The config points to schema files, selects the database dialect, and supplies connection credentials. generate writes SQL files from a schema diff; migrate applies recorded files; push changes the database directly and is a development convenience with a different review trail. Keep CI and local CLI versions aligned. Stable 0.45.x documentation and 1.0 release-candidate examples can disagree, particularly around relational queries, so check the installed version before copying code.

Our full-package browser build measured 27.6 KB minified and 8.5 KB gzipped. Tree shaking and a driver-specific import can make an application result different, but the disk install remains 17 MB because the package publishes many dialect and integration modules. There was no native build in the ORM itself. Runtime support depends on the driver: better-sqlite3 brings native binaries, pg expects TCP access, and edge adapters have their own transaction and connection constraints. Version 0.45.2 should be the floor because it fixes identifier escaping.

Patterns

Define a PostgreSQL table declare-postgres-table

import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const accounts = pgTable("accounts", {
  id: integer().primaryKey().generatedAlwaysAsIdentity(),
  email: text().notNull().unique(),
  createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(),
});

Column builders are dialect-specific. Do not mix pg-core builders into a MySQL or SQLite schema.

Connect through node-postgres connect-postgres

import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema.js";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle({ client: pool, schema });

Install pg separately. Supplying schema enables the relational db.query API and does not replace pool sizing or TLS configuration.

Filter and limit a select select-filtered-rows

import { and, eq, gt } from "drizzle-orm";

const rows = await db
  .select({ id: accounts.id, email: accounts.email })
  .from(accounts)
  .where(and(eq(accounts.active, true), gt(accounts.id, 100)))
  .limit(25);

The selected object controls the inferred result shape. Conditions use imported SQL operators rather than a filter object.

Insert and return a PostgreSQL row insert-returned-row

const [account] = await db
  .insert(accounts)
  .values({ email: "ada@example.com" })
  .returning();

PostgreSQL and SQLite support returning(). For MySQL, use the dialect's $returningId() behavior or issue a separate select.

Update rows behind a condition update-safely

import { eq } from "drizzle-orm";

await db
  .update(accounts)
  .set({ active: false })
  .where(eq(accounts.id, accountId));

The ORM permits an update without where(), which changes every row. Add an application guard when bulk updates are never valid.

Delete one row by primary key delete-safely

import { eq } from "drizzle-orm";

const removed = await db
  .delete(accounts)
  .where(eq(accounts.id, accountId))
  .returning({ id: accounts.id });

A missing where() deletes the full table. returning() is dialect-dependent and is unavailable on MySQL in this form.

Join with an explicit result shape join-projected-columns

const rows = await db
  .select({
    accountEmail: accounts.email,
    invoiceTotal: invoices.totalCents,
  })
  .from(accounts)
  .leftJoin(invoices, eq(invoices.accountId, accounts.id));

A left join makes the joined fields nullable in the inferred type. The projection stays flat unless you deliberately nest its object keys.

Load a relation with the stable query API query-relations

const rows = await db.query.accounts.findMany({
  with: { invoices: true },
  where: (account, { eq }) => eq(account.active, true),
  limit: 20,
});

This shape targets stable 0.45.x and requires schema plus relation definitions in drizzle(). The 1.0 candidate has different relational-query material.

Commit several statements together run-transaction

await db.transaction(async (tx) => {
  await tx.update(accounts)
    .set({ balanceCents: sql`${accounts.balanceCents} - ${amount}` })
    .where(eq(accounts.id, sourceId));
  await tx.update(accounts)
    .set({ balanceCents: sql`${accounts.balanceCents} + ${amount}` })
    .where(eq(accounts.id, destinationId));
});

Use tx for every statement inside the callback. Driver and platform capabilities determine isolation options and whether interactive transactions are supported.

Update on a PostgreSQL conflict upsert-postgres

await db.insert(accounts)
  .values({ email, displayName })
  .onConflictDoUpdate({
    target: accounts.email,
    set: { displayName },
  });

PostgreSQL and SQLite use onConflictDoUpdate. MySQL exposes onDuplicateKeyUpdate instead.

Parameterize a raw SQL expression compose-raw-sql

import { sql } from "drizzle-orm";

const result = await db.execute(sql`
  select date_trunc('day', ${events.createdAt}) as day, count(*)::int as total
  from ${events}
  where ${events.createdAt} >= ${since}
  group by 1
`);

Values interpolated through the sql template become parameters. Use sql.identifier only for identifier data and stay on 0.45.2 or newer for its escaping fix.

Generate and apply migration files manage-migrations

# drizzle.config.ts identifies dialect, schema, output, and credentials
npx drizzle-kit generate
npx drizzle-kit migrate

generate creates reviewable SQL from schema changes; migrate applies recorded files. drizzle-kit push writes changes directly and is a different production-control choice.

Alternatives

PackageRegistryPick it when
prismanpmChoose it when a schema DSL, generated client, nested relation API, and broader onboarding material matter more than a thin SQL-shaped layer
kyselynpmChoose it when you want a typed query builder and prefer to keep schema declaration and migration policy elsewhere
sequelizenpmChoose it for an established JavaScript application that relies on model instances, hooks, and its long-running plugin ecosystem
typeormnpmChoose it when decorator-based entities and Data Mapper or Active Record patterns already define the codebase

More data guides

numpy · fsspec · pandas · pyarrow · sqlalchemy · s3fs · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.