Drizzle ORM

Drizzle ORM connects application queries and schema definitions with TypeScript's type system while keeping SQL concepts visible. Choose the database and driver first, then verify the queries, migrations, and transaction boundaries your application needs. A typed query is useful assistance, not a promise that stored data or user input is valid.

The practical benefit is a clearer path from application intent to database operations. Preserve that clarity by inspecting generated SQL and treating a migration as an operational change, not a side effect of editing a schema file.

TL;DR

Quick Example

This complete JavaScript example uses Drizzle ORM 0.45.4 and @libsql/client 0.18.0 on Node 24. Install those exact packages in a disposable directory and save the code as example.mjs. The local in-memory libSQL database persists nothing and contacts no remote service. Newer documentation also describes release-candidate APIs; this example is scoped to the installed versions.

Expect exactly one remaining row. The deliberate failure verifies that the second insert is rolled back; the duplicate primary key must also be rejected. The schema is created explicitly here to keep the fixture complete. This does not run Drizzle Kit or prove that a production schema was migrated correctly.

Core Concepts

Schema typing describes the application view

Column declarations help infer query and result types. They do not apply DDL just by existing in a source file, and they do not authenticate external JSON. Keep a runtime validation boundary before accepting values and retain database constraints for durable invariants. The SQLite driver guide distinguishes supported connection options; driver differences matter for available operations and execution behavior.

Transactions belong to a connection context

The transaction callback supplies a handle for the intended unit of work. Use that handle consistently inside the callback rather than accidentally issuing a write through an unrelated outer database object. The transactions guide explains commit and rollback composition. Verify the selected driver's behavior, especially when a service adapter or remote connection changes the boundary.

Migrations are reviewed change artifacts

Drizzle Kit can generate SQL from schema differences. The generate reference describes comparing snapshots and producing migration files. Generating a file, applying it, and verifying the resulting database are three separate events. A generated operation can still need data repair, backfill, lock planning, or a compatible application rollout.

Follow One Change from Type to Database

Use a realistic populated database for the rehearsal. An empty table can make a new required column or index look easy while hiding the business repair and operating cost. Decide the allowed defaults and invalid-record treatment with the data owner before generating a reassuring success report.

A rollback script is not always the safest reversal. Dropping a column after new writers have populated it can lose information, and reverting application code may not restore old data semantics. Prefer an explicitly reviewed expand-and-contract sequence where the workload permits it, with compatibility checks for old and new writers.

The exact procedure depends on the target engine and driver. The in-memory fixture checks one libSQL transaction path; it says nothing about PostgreSQL locking, MySQL isolation, network ambiguity, or concurrent writers. Move those conditions into the acceptance test rather than relying on a shared SQL vocabulary.

Inspect the Query Boundary

Typed query builders can make selected columns and joins visible, but expensive scans, missing indexes, and unbounded result sets remain database questions. Inspect generated SQL and representative query plans in the target database. Record cardinality and filter selectivity when comparing query shapes.

Keep raw SQL intentional and parameterized. A type assertion does not sanitize a string pasted into query text. When moving SQL through a tagged or parameterized API, verify which parts are values and which parts are identifiers or syntax; those require different handling.

Errors need an application contract as well. Translate a known uniqueness failure into a stable response where appropriate, retain actionable internal diagnostics, and avoid exposing sensitive query values. Test a failed transaction so callers do not receive success before the database unit of work has actually committed.

Best Practices

Pin the driver path

Record the database engine, driver, ORM, and migration-tool versions with the example. A method available in a release candidate may differ from the stable API installed in a repository. Match examples to the package declarations and execute them before claiming support.

Keep rollback tests meaningful

Write one valid control row, fail after a second write within the transaction, then read through the intended database interface. This catches a common mistake: testing only that an exception was thrown while leaving the partial write unexamined.

Validate outside the compiler

Parse and validate user inputs and external messages at runtime. Compile-time types help developers work with expected shapes; database constraints and application authorization still protect different invariants. Exercise malformed values through the actual request boundary.

Review SQL before application

Treat generated migrations as code requiring a diff, owner, test data, operating window where needed, and recovery route. Never infer that a generated file is harmless because its source declaration looks small.

Common Mistakes

Bad: Assume editing sqliteTable changes an existing database.

Correct: Generate/review/apply the intended migration and inspect the resulting schema.

Bad: Use the outer db handle for a write inside a transaction callback.

Correct: Keep the intended unit on tx and verify partial writes disappear after failure.

Bad: Treat TypeScript types as validation of incoming JSON.

Correct: Validate values at runtime and retain database and access-control rules.

FAQ

Does Drizzle hide SQL completely?

Its schema and query APIs keep many SQL concepts explicit. Inspect generated queries and plans when correctness or cost depends on the actual statement.

Is the manual CREATE TABLE a migration workflow?

No. It makes this disposable fixture complete. A production change needs its own reviewed migration lifecycle.

Does this validate every Drizzle driver?

No. The scope is the named local libSQL path. Other engines, drivers, concurrency, and remote failure behavior remain pending.

When is another ORM the better choice?

Compare the team’s query model, required abstractions, database features, migration practices, and operating effort using representative work. This article makes no universal ranking.

Related Topics

References