Objection.js models with Knex on SQLite: working versions (tested 2026-10-15)
Use Objection.js Model classes on a Knex better-sqlite3 connection with relationMappings, inserts and eager graph fetches in offline tests.
passed1 passed · 0 failed2026-10-15 16:02 UTC- Status
- passed
- Last passed
- 2026-10-15 16:02 UTC
- Tests
- 1 / 1
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38764699834
The code
Exactly the files that passed. Your agent can pull them with get_recipe("objection-knex-sqlite") (install), or see them on GitHub.
{
"name": "objection-knex-sqlite",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"objection": "3.9.1",
"knex": "3.3.0",
"better-sqlite3": "13.0.3"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import Knex from "knex";
import { Model } from "objection";
export class User extends Model {
id!: number;
email!: string;
posts?: Post[];
static tableName = "users";
static relationMappings = {
posts: {
relation: Model.HasManyRelation,
modelClass: () => Post,
join: { from: "users.id", to: "posts.userId" },
},
};
}
export class Post extends Model {
id!: number;
title!: string;
userId!: number;
static tableName = "posts";
}
export async function createDb() {
const knex = Knex({
client: "better-sqlite3",
connection: { filename: ":memory:" },
useNullAsDefault: true,
});
Model.knex(knex);
await knex.schema.createTable("users", (t) => {
t.increments("id").primary();
t.string("email").notNullable().unique();
});
await knex.schema.createTable("posts", (t) => {
t.increments("id").primary();
t.string("title").notNullable();
t.integer("userId").notNullable().references("users.id");
});
return knex;
}
export async function createUserWithPosts(email: string, titles: string[]) {
return User.query().insertGraph({
email,
posts: titles.map((title) => ({ title })),
});
}
import { test } from "node:test";
import assert from "node:assert/strict";
import { createDb, createUserWithPosts, User } from "../src/db.ts";
test("insertGraph and eager fetch", async () => {
const knex = await createDb();
await createUserWithPosts("[email protected]", ["One", "Two"]);
const user = await User.query().where("email", "[email protected]").withGraphFetched("posts").first();
assert.ok(user);
assert.equal(user!.posts!.length, 2);
assert.deepEqual(user!.posts!.map((p) => p.title).sort(), ["One", "Two"]);
await knex.destroy();
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Knex migrations, seeds and queries on better-sqlite3
Knex 3 with the better-sqlite3 client: in-code migrations via a custom migrationSource (latest, down, rollback, list), foreign keys enabled per connection, a transactional seed and a join plus aggregate query, tested on in-memory SQLite.
better-sqlite3 transactions with Zod-validated inputs
A small ledger on better-sqlite3 using db.transaction for atomic transfers, nested transactions as savepoints for all-or-nothing batches, and Zod 4 to validate inputs and rows, tested on in-memory SQLite.
Drizzle ORM + better-sqlite3 CRUD
Typed schema, migrations-free table creation and CRUD with Drizzle on an in-memory SQLite database.
Drizzle ORM + drizzle-zod validated inserts on SQLite
Generate Zod insert and select schemas from Drizzle tables with drizzle-zod, refine them, and validate input before inserting into SQLite.
All objection recipesAll knex recipesAll better-sqlite3 recipes