Node.js 46 🟢 Lightweight Type-Safe Database Querying with Drizzle ORM
Drizzle ORM is a TypeScript ORM built on a different philosophy than the traditional data frameworks. It does not try to abstract SQL away. It embraces SQL and provides a thin, type-safe layer on top of it. The schema is defined in TypeScript, the queries look like SQL, and the generated SQL is exactly what you would write by hand. The result is a library that is lightweight, serverless-ready, and predictable. Drizzle is approximately 7.4 kB minified and gzipped, has zero runtime dependencies, and is tree-shakeable. It supports every PostgreSQL, MySQL, and SQLite database, including serverless platforms like Turso, Neon, PlanetScale, and Cloudflare D1 .
The core insight is that the double learning curve of the traditional ORMs is unnecessary. With Drizzle, if you know SQL, you know Drizzle. The query builder uses the SQL-like syntax, and the generated query is what you would expect. The select, the insert, the update, and the delete map directly to the SQL statements. The schema is declared with the pgTable, the mysqlTable, or the sqliteTable functions, and the columns are the TypeScript objects with the type inference. The type safety is the compile-time, and the runtime is the thin .
The Drizzle ecosystem has three parts. The drizzle-orm is the runtime library. The drizzle-kit is the CLI for the migrations and the introspection. The Drizzle Studio is the visual browser for the database. The drizzle-kit generate creates the SQL migration files from the schema changes, and the drizzle-kit migrate applies them. The migrations are the plain SQL, and they can be reviewed before the application .
This chapter covers three areas. First, why Drizzle exists — the abstraction problem and the SQL-like approach. Second, how to define the schema and the queries — the table declaration, the column types, the relations, and the CRUD operations. Third, the migrations and the patterns — the drizzle-kit, the generate and the migrate, the relational queries, and the type inference. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the Drizzle workflow.
Key point: Drizzle ORM is a lightweight, type-safe ORM that embraces SQL. The schema is defined in TypeScript with the pgTable, the mysqlTable, or the sqliteTable. The queries use the SQL-like builder or the relational query API. The drizzle-kit generates and applies the migrations. Drizzle has zero runtime dependencies, supports every PostgreSQL, MySQL, and SQLite database, and is serverless-ready by design.
Why Drizzle exists
The abstraction problem. The traditional ORMs hide the SQL behind the object-oriented API. The developer writes the user.save() instead of the INSERT INTO users, and the ORM generates the SQL. The abstraction is convenient, but it introduces the double learning curve: the developer must know both SQL and the ORM’s API. The debugging requires the understanding of the generated SQL, and the performance tuning requires the inspection of the query plan. Drizzle removes the abstraction. The query builder is the SQL-like, and the generated SQL is the predictable .
The type-safety problem. The TypeScript ORMs provide the type safety, but the level of the inference varies. The Drizzle’s inference is the full: the $inferSelect and the $inferInsert types are derived from the schema, and the query results are typed. The db.select().from(users) returns the User[], and the db.insert(users).values(...) accepts the NewUser. The compiler catches the typos, the wrong types, and the missing fields .
The serverless problem. The serverless databases — the Neon, the PlanetScale, the Turso, the Cloudflare D1 — require the lightweight client. The traditional ORMs often ship the heavy runtime, the Rust binaries, or the serverless adapters. Drizzle is the serverless-ready by design: it has the zero dependencies, the small bundle, and it works in every JavaScript runtime including the Node.js, the Bun, the Deno, the Cloudflare Workers, and the edge runtimes .
The migration problem. The Drizzle’s drizzle-kit generates the SQL migration files from the schema changes. The migrations are the plain SQL, and they can be reviewed, versioned, and applied in the order. The drizzle-kit generate creates the migration, and the drizzle-kit migrate applies it. The __drizzle_migrations table tracks the applied migrations, and the drizzle-kit push is the development shortcut that skips the migration files .
The relational-query problem. The Drizzle provides the two query APIs. The SQL-like API is the db.select().from(users).leftJoin(posts, eq(users.id, posts.userId)), and the relational API is the db.query.users.findMany({ with: { posts: true } }). The relational API returns the nested data in the single SQL query, and the developer does not worry about the joins or the data mapping .
The trade-off. Drizzle is the thin layer, and the thin layer means the developer writes the SQL-like queries. The relational API is the optional, and the SQL-like API is the default. The trade-off is between the control of the SQL and the convenience of the relational API, and Drizzle provides both.
a. The schema definition
The schema is defined in TypeScript with the dialect-specific functions. The pgTable is for the PostgreSQL, the mysqlTable is for the MySQL, and the sqliteTable is for the SQLite.
import { pgTable, serial, text, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 256 }).notNull().unique(),
name: text('name').notNull(),
active: boolean('active').default(true).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
The pgTable defines the table, and the columns are the TypeScript objects. The serial is the auto-incrementing integer, the varchar is the variable-length string, the text is the unlimited string, the timestamp is the date and time, and the boolean is the true/false .
The snake_case mapping. The database uses the snake_case, and the TypeScript uses the camelCase. The snakeCase builder maps the TypeScript keys to the database columns automatically:
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
fullName: text('full_name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
}, (table) => ({
// the indexes
}));
The fullName in the TypeScript maps to the full_name in the database .
The relations. The relations function defines the application-level relations. The one and the many describe the cardinality:
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').notNull().references(() => users.id),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
The usersRelations defines the one-to-many from the users to the posts, and the postsRelations defines the many-to-one from the posts to the users. The fields and the references specify the foreign key .
The indexes. The indexes are defined in the third argument of the table function:
import { index, uniqueIndex } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 256 }).notNull(),
}, (table) => ({
emailIdx: uniqueIndex('idx_users_email').on(table.email),
nameIdx: index('idx_users_name').on(table.name),
}));
The uniqueIndex creates the unique index, and the index creates the regular. The indexes are the important for the query performance .
b. The queries
The insert. The db.insert creates the rows:
import { db } from './db';
import { users } from './schema';
const newUser = await db
.insert(users)
.values({ email: 'alice@example.com', name: 'Alice' })
.returning();
console.log(newUser[0].id);
The .returning() returns the inserted row, which is the PostgreSQL and the SQLite feature. The MySQL uses the $returningId() or the separate select .
The select. The db.select reads the rows:
import { eq, and, like, gt, desc } from 'drizzle-orm';
const allUsers = await db.select().from(users);
const user = await db
.select()
.from(users)
.where(eq(users.email, 'alice@example.com'))
.limit(1);
const filtered = await db
.select()
.from(users)
.where(and(like(users.email, '%@example.com'), gt(users.createdAt, new Date('2024-01-01'))))
.orderBy(desc(users.createdAt))
.limit(10);
The eq is the equality, the and combines the conditions, the like is the pattern, the gt is the greater than, and the desc is the descending order .
The update. The db.update modifies the rows:
await db
.update(users)
.set({ name: 'Alice Smith' })
.where(eq(users.id, userId));
The delete. The db.delete removes the rows:
await db.delete(users).where(eq(users.id, userId));
The relational queries. The db.query API returns the nested data:
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
const userWithPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
where: eq(posts.published, true),
orderBy: desc(posts.createdAt),
limit: 5,
},
},
});
The findMany returns the array, and the findFirst returns the single. The with includes the relations, and the nested where, the orderBy, and the limit are the supported .
The partial select. The columns parameter limits the returned fields:
const names = await db
.select({ id: users.id, name: users.name })
.from(users);
The partial select reduces the data transferred from the database .
The transactions. The db.transaction runs the multiple queries in the transaction:
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ email: 'alice@example.com', name: 'Alice' })
.returning();
await tx.insert(posts).values({ title: 'First Post', authorId: user.id });
});
The tx is the transaction object, and the queries inside the callback are the transactional. The commit happens when the callback resolves, and the rollback happens when it throws .
c. The migrations
The drizzle.config.ts. The configuration specifies the schema path, the output directory, and the dialect:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
});
The schema is the path to the schema file, the out is the migration output directory, and the dialect is the database type .
The generate. The drizzle-kit generate creates the migration from the schema changes:
npx drizzle-kit generate
The command compares the current schema with the last known state and generates the SQL migration file. The output is the .sql file in the drizzle directory .
The migrate. The drizzle-kit migrate applies the pending migrations:
npx drizzle-kit migrate
The command runs the pending migration files against the database and tracks the applied migrations in the __drizzle_migrations table .
The push. The drizzle-kit push applies the schema changes directly to the database, skipping the migration files:
npx drizzle-kit push
The push is the development shortcut, and it should not be used in the production, because it bypasses the migration history .
The Studio. The drizzle-kit studio opens the visual browser:
npx drizzle-kit studio
The Studio shows the tables, the columns, and the data, and it allows the editing .
Complete Example Session
// ============================================
// PART 1: THE SCHEMA
// ============================================
// src/db/schema.ts
import { pgTable, serial, text, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 256 }).notNull().unique(),
name: text('name').notNull(),
active: boolean('active').default(true).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content').notNull(),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// ============================================
// PART 2: THE TYPE INFERENCE
// ============================================
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
// The $inferSelect is the read type, and the $inferInsert is the write type.
// ============================================
// PART 3: THE DATABASE CONNECTION
// ============================================
// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const client = postgres(process.env.DATABASE_URL);
export const db = drizzle(client, { schema });
// ============================================
// PART 4: THE INSERT
// ============================================
const newUser = await db
.insert(users)
.values({ email: 'alice@example.com', name: 'Alice' })
.returning();
console.log(newUser[0].id);
// ============================================
// PART 5: THE SELECT
// ============================================
import { eq, and, like, gt, desc } from 'drizzle-orm';
const allUsers = await db.select().from(users);
const user = await db
.select()
.from(users)
.where(eq(users.email, 'alice@example.com'))
.limit(1);
const filtered = await db
.select()
.from(users)
.where(and(like(users.email, '%@example.com'), gt(users.createdAt, new Date('2024-01-01'))))
.orderBy(desc(users.createdAt))
.limit(10);
// ============================================
// PART 6: THE UPDATE AND THE DELETE
// ============================================
await db
.update(users)
.set({ name: 'Alice Smith' })
.where(eq(users.id, userId));
await db.delete(users).where(eq(users.id, userId));
// ============================================
// PART 7: THE RELATIONAL QUERY
// ============================================
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
const userWithPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
where: eq(posts.published, true),
orderBy: desc(posts.createdAt),
limit: 5,
},
},
});
// ============================================
// PART 8: THE TRANSACTION
// ============================================
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ email: 'bob@example.com', name: 'Bob' })
.returning();
await tx.insert(posts).values({
title: 'First Post',
content: 'Hello world',
authorId: user.id,
});
});
// ============================================
// PART 9: THE MIGRATIONS
// ============================================
// The configuration
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
});
// The generate
// npx drizzle-kit generate
// The migrate
// npx drizzle-kit migrate
// The studio
// npx drizzle-kit studio
// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================
// The schema
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 256 }).notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').notNull().references(() => users.id),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// The query with the relations
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// The type
type UserWithPosts = Awaited<ReturnType<typeof usersWithPosts>>;
// The type is inferred: the User with the posts array.
The ten parts show the schema, the type inference, the connection, the insert, the select, the update and the delete, the relational query, the transaction, the migrations, and the complete example.
Quick Reference
The Packages
| Package | The purpose |
|---|---|
drizzle-orm | The runtime library |
drizzle-kit | The CLI for the migrations and the introspection |
drizzle-zod | The Zod integration |
| The drivers | The postgres, the mysql2, the better-sqlite3, the @neondatabase/serverless |
The Table Functions
| The dialect | The function |
|---|---|
| PostgreSQL | pgTable |
| MySQL | mysqlTable |
| SQLite | sqliteTable |
The Column Types
| The PostgreSQL | The MySQL | The SQLite |
|---|---|---|
serial | int | integer |
varchar | varchar | text |
text | text | text |
timestamp | timestamp | integer |
boolean | boolean | integer |
integer | int | integer |
The Query Methods
| The method | The purpose |
|---|---|
db.select() | The select |
db.insert() | The insert |
db.update() | The update |
db.delete() | The delete |
db.query | The relational query |
db.transaction() | The transaction |
The Operators
| The operator | The purpose |
|---|---|
eq | The equality |
and | The AND |
or | The OR |
like | The pattern |
gt | The greater than |
desc | The descending |
asc | The ascending |
The drizzle-kit Commands
| The command | The purpose |
|---|---|
drizzle-kit generate | The generate the migration |
drizzle-kit migrate | The apply the migration |
drizzle-kit push | The development shortcut |
drizzle-kit studio | The visual browser |
drizzle-kit check | The schema drift |
Best Practices
✅ Do This:
// Use the schema as the source of truth
export const users = pgTable('users', { ... }); // ✅
// Use the $inferSelect and $inferInsert
export type User = typeof users.$inferSelect; // ✅
// Use the .returning() for the insert
await db.insert(users).values(...).returning(); // ✅
// Use the relations for the relational queries
const users = await db.query.users.findMany({ with: { posts: true } }); // ✅
// Use the transactions for the related inserts
await db.transaction(async (tx) => { ... }); // ✅
// Generate the migrations for the production
// npx drizzle-kit generate && npx drizzle-kit migrate // ✅
// Use the partial select for the large tables
const names = await db.select({ id: users.id, name: users.name }); // ✅
❌ Don’t Do This:
// Don't forget the .returning()
await db.insert(users).values(...); // the no data returned // ❌
// Don't skip the transactions for the related inserts
await db.insert(users).values(...);
await db.insert(posts).values(...); // the partial failure // ❌
// Don't use the db:push in the production
// npx drizzle-kit push // the bypasses the migrations // ❌
// Don't manually edit the generated migration
// the create a new one instead // ❌
// Don't delete the applied migrations
// the __drizzle_migrations tracks them // ❌
// Don't forget the indexes
// the query performance degrades // ❌
// Don't use the raw SQL when the builder works
await db.execute(sql`SELECT * FROM users`); // the builder is the safer // ❌
Common Pitfalls
| The pitfall | The why | The fix |
|---|---|---|
| The insert returns no data | The missing .returning() | Add the .returning() |
| The partial failure | The no transaction | Use the db.transaction |
| The schema drift | The manual change | Use the drizzle-kit check |
| The missing index | The no index in the schema | Add the index() |
| The relation not inferred | The missing relationName | Add the relationName |
| The migration fails | The SQL error | Review the generated SQL |
| The production push | The db:push | Use the generate and the migrate |
Real-World Examples
1. The Basic Select
const users = await db.select().from(users);
2. The Filter
const user = await db.select().from(users).where(eq(users.email, 'a@b.com'));
3. The Insert
await db.insert(users).values({ email: 'a@b.com', name: 'A' }).returning();
4. The Update
await db.update(users).set({ name: 'B' }).where(eq(users.id, 1));
5. The Delete
await db.delete(users).where(eq(users.id, 1));
6. The Relational
const users = await db.query.users.findMany({ with: { posts: true } });
7. The Transaction
await db.transaction(async (tx) => { ... });
8. The Partial Select
const names = await db.select({ id: users.id, name: users.name }).from(users);
9. The Index
(table) => ({ emailIdx: uniqueIndex('idx_users_email').on(table.email) })
10. The Type
type User = typeof users.$inferSelect;
Visual
The Drizzle Workflow
┌──────────────────────────────────────────────────────────────┐
│ THE DRIZZLE WORKFLOW │
│ │
│ The schema: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ src/db/schema.ts │ │
│ │ export const users = pgTable('users', { ... }); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ drizzle-kit generate │
│ ▼ │
│ The migration: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ drizzle/0000_initial.sql │ │
│ │ CREATE TABLE users (...); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ drizzle-kit migrate │
│ ▼ │
│ The database: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PostgreSQL, MySQL, SQLite, ... │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ drizzle-orm │
│ ▼ │
│ The application: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ const users = await db.select().from(users); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The SQL-Like vs the Relational
┌──────────────────────────────────────────────────────────────┐
│ THE SQL-LIKE vs THE RELATIONAL │
│ │
│ THE SQL-LIKE: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ db.select() │ │
│ │ .from(users) │ │
│ │ .leftJoin(posts, eq(users.id, posts.authorId)) │ │
│ │ │ │
│ │ The explicit, the SQL-like, the full control. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ THE RELATIONAL: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ db.query.users.findMany({ │ │
│ │ with: { posts: true }, │ │
│ │ }) │ │
│ │ │ │
│ │ The nested data, the no joins, the convenience. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The both output the single SQL query. │
│ │
└──────────────────────────────────────────────────────────────┘
The Relations
┌──────────────────────────────────────────────────────────────┐
│ THE RELATIONS │
│ │
│ export const users = pgTable('users', { ... }); │
│ export const posts = pgTable('posts', { ... }); │
│ │
│ export const usersRelations = relations(users, ({ many }) => ({
│ posts: many(posts), │
│ })); │
│ │
│ export const postsRelations = relations(posts, ({ one }) => ({
│ author: one(users, { │
│ fields: [posts.authorId], │
│ references: [users.id], │
│ }), │
│ })); │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The query: │ │
│ │ const users = await db.query.users.findMany({ │ │
│ │ with: { posts: true }, │ │
│ │ }); │ │
│ │ │ │
│ │ The result: the User[] with the posts array. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Type Inference
┌──────────────────────────────────────────────────────────────┐
│ THE TYPE INFERENCE │
│ │
│ export const users = pgTable('users', { │
│ id: serial('id').primaryKey(), │
│ email: varchar('email', { length: 256 }).notNull(), │
│ name: text('name').notNull(), │
│ }); │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ export type User = typeof users.$inferSelect; │ │
│ │ // { id: number, email: string, name: string } │ │
│ │ │ │
│ │ export type NewUser = typeof users.$inferInsert; │ │
│ │ // { email: string, name: string, id?: number } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The types are inferred from the schema, and the compiler │
│ catches the typos and the wrong types. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Drizzle ORM | The lightweight, type-safe ORM |
| The bundle | ~7.4 kB minified+gzipped |
| The dependencies | Zero |
| The dialects | PostgreSQL, MySQL, SQLite |
| The schema | The pgTable, the mysqlTable, the sqliteTable |
| The queries | The SQL-like and the relational |
| The migrations | The drizzle-kit generate and the migrate |
| The type inference | The $inferSelect and the $inferInsert |
| The relations | The relations function |
| The serverless | The Neon, the PlanetScale, the Turso, the D1 |
Key takeaways:
- Drizzle ORM is a lightweight, type-safe ORM that embraces SQL. The schema is defined in TypeScript, and the queries look like SQL. The generated SQL is the predictable, and the type inference is the full .
- The schema is the source of truth. The
pgTable, themysqlTable, and thesqliteTabledefine the tables, and the$inferSelectand the$inferInsertderive the types. Therelationsfunction defines the application-level relations . - The queries use the SQL-like or the relational API. The
db.select(), thedb.insert(), thedb.update(), and thedb.delete()are the SQL-like. Thedb.queryis the relational, and it returns the nested data in the single query . - The
drizzle-kitgenerates and applies the migrations. Thegeneratecreates the SQL migration from the schema changes, and themigrateapplies it. Thepushis the development shortcut . - The type inference is the full. The
Userand theNewUsertypes are derived from the schema, and the compiler catches the typos and the wrong types . - The relations are the application-level, not the database-level. The
relationsfunction defines the one-to-one, the one-to-many, and the many-to-many. The foreign keys are the separate . - Drizzle is the serverless-ready. The zero dependencies, the small bundle, and the support for the Neon, the PlanetScale, the Turso, and the D1 make it the choice for the serverless and the edge .
- The migrations are the plain SQL. The
drizzle-kit generateproduces the.sqlfiles, and they can be reviewed before the application. The__drizzle_migrationstable tracks the applied migrations .
Remember: Drizzle ORM is the lightweight, type-safe ORM that embraces SQL. The schema is the TypeScript, and the queries are the SQL-like or the relational. The drizzle-kit handles the migrations, and the type inference is the full. The result is the predictable, the performant, and the serverless-ready database layer, which is the foundation of the modern Node.js application.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!