# Migrating from Node and Drizzle

The concept map for a Node app on Drizzle ORM — Express, Fastify, Hono or Next.js route handlers in
front. The runbook — phases, exit conditions, stop conditions — is
[Migrating an existing app](/migrating-an-existing-app); this page says what each term becomes.
`As of 2026-11` ([orm.drizzle.team](https://orm.drizzle.team/)).

## Concept map

| Node / Drizzle | Ultimate |
|---|---|
| an Express / Fastify / Hono route that renders | a route file: [Routes and render modes](/routes-and-render-modes) |
| a route handler that writes, a tRPC mutation | an `action`: [Actions](/actions) |
| a tRPC query, a GET handler | a `query`: [Queries and live queries](/queries-and-live-queries) |
| a `pgTable()` declaration | an `entity()`: [Entities and migrations](/entities-and-migrations) |
| `drizzle-kit generate`, `migrate` | `x db gen "<name>"`, then `x db migrate` |
| a Zod schema on a handler | the action's `input`: [Actions](/actions) |
| BullMQ, pg-boss | a `job`: [Jobs and workflows](/jobs-and-workflows) |
| `node-cron`, a repeatable job | a `task`: [Scheduled tasks](/scheduled-tasks) |
| Passport, Auth.js, Lucia | [Auth](/auth) |
| CASL, hand-written middleware checks | a `policy`: [Policies and authz](/policies-and-authz) |
| Socket.IO, `ws` | a live query: [Realtime](/realtime) |
| Nodemailer | [Mail](/mail) |
| S3 SDK calls | [Storage and uploads](/storage-and-uploads) |
| Vitest, Jest | `bun test` through [Testing](/testing) |
| `node server.js`, `next dev` | `x dev` |

## What does not map

| Node / Drizzle | Why |
|---|---|
| Node-only APIs and native addons | Bun only: check each against Bun's own list, [bun.sh/docs/runtime/nodejs-apis](https://bun.sh/docs/runtime/nodejs-apis) |
| MySQL, SQLite (`mysqlTable`, `sqliteTable`) | Postgres only, by design: [Home](/docs) |
| React components | pages render with SolidJS; interactive parts are islands. No converter |
| a serverless or edge deploy target | containers only: [Deployment](/deployment) |

## Schema adoption

Drizzle already declares tables in TypeScript, so the shape carries over column by column; ids are
whatever the app chose. Phase 1 of the runbook declares the existing tables with the physical-name
overrides and type map it gives, then `x db gen` until `x verify --only drift` agrees:
[Migrating an existing app](/migrating-an-existing-app).

## Identity cutover

| The app stores | Branch |
|---|---|
| `bcrypt` / `bcryptjs` hashes (`$2a$`, `$2b$`) | **proceed**: verified natively, rewritten as argon2id on first login ([Migrating an existing app](/migrating-an-existing-app)) |
| `argon2` hashes (`$argon2id$`) | **proceed**: rehashed when weaker than the policy |
| scrypt, PBKDF2, anything else | **⛔ stop**: no verifier ships. Keep the old login as a bridge, or force a reset |
| a JWT or a session table | bridge it with your own verifier behind `configureAuthenticator()` ([Auth](/auth)) |
