Skip to main content

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

db
DrizzleDB
required
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.
relations
Relations
required
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:
format
(row: TableRow) => any
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,
    };
  },
});
where
WhereClause
Default where condition applied to all queries. Useful for soft deletes or tenant isolation.
const activeUsers = model("user", {
  where: { isDeleted: esc(false) },
});
methods
Record<string, Function>
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", {});

With Formatting

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:

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.

Build docs developers (and LLMs) love