ProvenPin Install

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
pinned versions5
objection@3.9.1knex@3.3.0better-sqlite3@13.0.3@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
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.

package.json
{
  "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"
  }
}
src/db.ts
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 })),
  });
}
test/db.test.ts
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.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.