Overview
The modelBuilder function creates a factory for building type-safe model instances from your Drizzle ORM schema. Each model provides a chainable API for queries, mutations, and result refinement.
Syntax
const model = modelBuilder({
db,
schema,
relations,
dialect,
});
Parameters
Drizzle database instance created via drizzle() from your chosen driver (node-postgres, better-sqlite3, etc.).
schema
Record<string, Table>
required
Your Drizzle schema object containing all table definitions.
Drizzle relations definition (v2 format). Required for .with() relation loading.See: Drizzle Relations v2
dialect
'PostgreSQL' | 'MySQL' | 'SQLite'
required
Database dialect. Affects SQL generation and mutation return behavior.
Return Value
Returns a model factory function with this signature:
(tableName: string, options?: ModelOptions) => Model
Model Options
The second parameter accepts optional configuration:
Transform function applied to every query result. Use for date parsing, field sanitization, or type coercion.const postModel = model("post", {
format(row) {
return {
...row,
createdAt: new Date(row.createdAt),
updatedAt: row.updatedAt ? new Date(row.updatedAt) : null,
};
},
});
Default where condition applied to all queries. Useful for soft deletes or tenant isolation.const activeUsers = model("user", {
where: { isDeleted: esc(false) },
});
Custom methods to attach to the model instance.const userModel = model("user", {
methods: {
async byEmail(email: string) {
return await this.where({ email: esc(email) }).findFirst();
},
},
});
// Usage
await userModel.byEmail("[email protected]");
Examples
Basic Setup
import { modelBuilder } from "@apisr/drizzle-model";
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "./schema";
import { relations } from "./relations";
const db = drizzle(process.env.DATABASE_URL!, { schema, relations });
const model = modelBuilder({
db,
schema,
relations,
dialect: "PostgreSQL",
});
// Create models
const userModel = model("user", {});
const postModel = model("post", {});
const postModel = model("post", {
format(row) {
return {
...row,
createdAt: new Date(row.createdAt),
updatedAt: row.updatedAt ? new Date(row.updatedAt) : null,
// Remove internal fields
internalStatus: undefined,
};
},
});
const post = await postModel.findFirst();
// post.createdAt is Date, not string
With Default Filters
const activeUsers = model("user", {
where: { isVerified: esc(true), isDeleted: esc(false) },
});
// All queries include the default filter
const users = await activeUsers.findMany();
// SELECT * FROM user WHERE is_verified = true AND is_deleted = false
With Custom Methods
const userModel = model("user", {
methods: {
async byEmail(email: string) {
return await this.where({ email: esc(email) }).findFirst();
},
async adults() {
return await this.where({ age: esc.gte(18) }).findMany();
},
},
});
const user = await userModel.byEmail("[email protected]");
const adults = await userModel.adults();
Model Methods
Once created, model instances expose the full query API:
- Query Methods -
findMany, findFirst, count, where
- Mutation Methods -
insert, update, delete, upsert
- Refinement Methods -
select, exclude, with, omit, raw, safe
- Transaction API -
.db() method
Type Safety
The model factory infers all types from your Drizzle schema:
// TypeScript knows all column names and types
await userModel.where({
name: esc("Alex"), // ✅ 'name' is a valid column
age: esc.gte(18), // ✅ 'age' is a number column
// invalid: esc("foo") // ❌ Type error: age expects number
}).findMany();
Notes
Requires Drizzle ORM beta versions (^1.0.0-beta.2-86f844e or later) with Relations v2.
The dialect parameter affects mutation return behavior. PostgreSQL and SQLite support .returning(), while MySQL uses insert ID fallback.