Skip to main content

@casekit/orm

A type-safe, PostgreSQL-native ORM for TypeScript with deep relational querying, middleware support, and declarative schema definitions.

Overview

@casekit/orm is a TypeScript ORM designed for PostgreSQL that prioritizes:

  • Full compile-time type safety for queries and results
  • Deep relational queries with 1:1, 1:N, N:1, and N:N relationships
  • Middleware system for query interception (multi-tenancy, timestamps, etc.)
  • TypeScript model definitions — no DSLs, just TypeScript
  • SQL for complex aggregations — we don't try to do everything in the ORM, but instead give you tools to type your SQL queries easily

The ORM uses LATERAL JOINs for efficient multi-relation queries and validates all results with Zod schemas at runtime.

Installation

npm add @casekit/orm @casekit/orm-cli @casekit/orm-migrate pg zod

Quick Example

import { orm, type Config, type ModelDefinition, $ilike } from "@casekit/orm";
import { sql } from "@casekit/sql";

// Define your models
const author = {
fields: {
id: { type: "serial", primaryKey: true },
name: { type: "text" },
email: { type: "text", unique: true },
},
relations: {
books: {
type: "1:N",
model: "book",
fromField: "id",
toField: "authorId",
},
},
} as const satisfies ModelDefinition;

const book = {
fields: {
id: { type: "serial", primaryKey: true },
title: { type: "text" },
authorId: {
type: "integer",
references: { model: "author", field: "id" },
},
},
relations: {
author: {
type: "N:1",
model: "author",
fromField: "authorId",
toField: "id",
},
},
} as const satisfies ModelDefinition;

// Create the ORM instance
const db = orm({
models: { author, book },
connection: { database: "myapp" },
});

await db.connect();

// Query with full type safety
const authorsWithBooks = await db.findMany("author", {
select: ["id", "name"],
include: {
books: {
select: ["id", "title"],
},
},
where: {
email: { [$ilike]: "%@example.com" },
},
});

Features

Type-Safe Queries

Every query is fully typed based on your model definitions. The select clause determines which fields are returned, and the include clause adds related records.

Relational Queries

Load related data efficiently with a single query using LATERAL JOINs:

const author = await db.findOne("author", {
select: ["id", "name"],
include: {
books: {
select: ["id", "title"],
where: { published: true },
orderBy: [["title", "asc"]],
limit: 10,
},
},
where: { id: 1 },
});

Middleware

Apply cross-cutting concerns like multi-tenancy or timestamps:

const dbWithMiddleware = db.middleware([
tenancy({ org: currentOrg }),
userstamps({ user: currentUser }),
]);

Raw SQL

For complex queries, use raw SQL with type safety:

import { z } from "zod";
import { sql } from "@casekit/sql";

const results = await db.query(
z.object({ total: z.number() }),
sql`SELECT COUNT(*) as total FROM authors`
);

Next Steps