# Drizzle ORM: an introduction

> Drizzle ORM is a TypeScript-first SQL toolkit. Learn schema definitions, queries, relations, and migrations with drizzle-kit for Node, Bun, and Cloudflare D1.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-02 | Topics: [Database](https://flaviocopes.com/tags/database/) | Canonical: https://flaviocopes.com/drizzle/

Most apps need a database. You want type safety, but you don't want to fight your ORM every time you write a join.

**Drizzle ORM** is what I reach for. It's a [TypeScript](https://flaviocopes.com/typescript/) library that stays close to [SQL](https://flaviocopes.com/sql-introduction/). If you know SQL, you know Drizzle.

Prisma takes a different path. You write a schema in its own DSL, run codegen, and query through a generated client. Drizzle is just TypeScript. Your tables are TypeScript objects. Your queries look like SQL. No codegen step for the client.

Install it with:

```bash
npm i drizzle-orm
```

## Defining a schema

You describe tables in a `schema.ts` file. Each table is a function call with column definitions.

For SQLite or Cloudflare D1, use `sqliteTable`:

```ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull(),
  email: text('email').notNull(),
})
```

On Postgres, swap `sqliteTable` for `pgTable` and import from `drizzle-orm/pg-core`. The shape is the same.

## Querying

You create a `db` instance by passing your database driver to `drizzle()`. On Node with Postgres, that looks like `drizzle(pool)`. On D1, you pass the binding: `drizzle(env.DB)`.

Then you query with a builder that mirrors SQL. `select()`, `from()`, `where()` — same words you'd use in a SQL statement.

Select every row:

```ts
const allUsers = await db.select().from(users)
```

Filter with `where()` and `eq()`:

```ts
import { eq } from 'drizzle-orm'

const flavio = await db
  .select()
  .from(users)
  .where(eq(users.email, 'flavio@flaviocopes.com'))
```

Insert a row:

```ts
await db.insert(users).values({
  name: 'Flavio',
  email: 'flavio@flaviocopes.com',
})
```

Update a row:

```ts
await db
  .update(users)
  .set({ name: 'Flavio Copes' })
  .where(eq(users.email, 'flavio@flaviocopes.com'))
```

Delete a row:

```ts
await db
  .delete(users)
  .where(eq(users.email, 'flavio@flaviocopes.com'))
```

Every method returns typed results. Change a column name in the schema and TypeScript catches the broken query at compile time.

## Relational queries

Manual joins work, but Drizzle has a nicer API for related data. Define the relationship once:

```ts
import { relations } from 'drizzle-orm'

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  userId: integer('user_id').notNull().references(() => users.id),
})

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}))
```

Pass the schema when you create `db`. Then fetch users with their posts in one call:

```ts
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
})
```

No hand-written `JOIN`. Drizzle figures out the SQL for you.

## Migrations with drizzle-kit

Schema changes need migrations. **drizzle-kit** is the CLI that handles them.

Install it:

```bash
npm i -D drizzle-kit
```

Add a config file:

```ts
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'sqlite',
})
```

When you change the schema, generate a SQL migration:

```bash
npx drizzle-kit generate
```

This writes a `.sql` file you can read and commit. You see exactly what will run against the database. No surprises.

Apply it to the database:

```bash
npx drizzle-kit migrate
```

For quick prototyping, skip the migration files and push the schema directly:

```bash
npx drizzle-kit push
```

Want to browse your data in a GUI? Run Drizzle Studio:

```bash
npx drizzle-kit studio
```

It opens a local UI where you can inspect tables and run queries.

## Where it runs

Drizzle works anywhere TypeScript runs. Node, Bun, Deno, serverless, edge. It supports Postgres, MySQL, SQLite, and a growing list of hosted databases.

I use it with [Cloudflare D1](https://flaviocopes.com/cloudflare-d1/) on Workers. D1 is SQLite at the edge, and Drizzle has a first-class driver for it. The setup is one import:

```ts
import { drizzle } from 'drizzle-orm/d1'

const db = drizzle(env.DB, { schema })
```

If you want the full Astro + D1 + Drizzle stack, I wrote a separate guide: [Astro with D1 and Drizzle](https://flaviocopes.com/astro-d1-drizzle/).

## Why Drizzle

My advice: pick an ORM that respects SQL, not one that hides it.

Drizzle queries read like SQL. Your existing database knowledge transfers directly. There's no magic layer between you and the database.

That's why it's my default for new TypeScript projects.
