Bulk Operations
insertMany
Insert multiple rows in a single SQL statement:
await Product.query().insertMany([
{ sku: "W-001", name: "Widget", price: 999, stock: 100 },
{ sku: "G-001", name: "Gadget", price: 1499, stock: 50 },
{ sku: "D-001", name: "Doohickey", price: 249, stock: 200 },
]);INSERT INTO products (sku, name, price, stock)
VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)
-- params: ["W-001", "Widget", 999, 100, "G-001", "Gadget", 1499, 50, "D-001", "Doohickey", 249, 200]insertMany returns the inserted rows on PostgreSQL (where RETURNING is available). On SQLite, it returns an empty array — use a follow-up query if you need the inserted rows.
onConflict
Both insert() and insertMany() support conflict resolution via .onConflict(columns):
doNothing
Skip rows that violate a unique constraint:
await Product.query()
.insertMany([
{ sku: "W-001", name: "Updated", price: 888, stock: 999 }, // skipped (sku exists)
{ sku: "T-001", name: "Thingamajig", price: 599, stock: 75 }, // inserted
])
.onConflict(["sku"])
.doNothing();INSERT INTO products (sku, name, price, stock) VALUES (?, ?, ?, ?), (?, ?, ?, ?)
ON CONFLICT (sku) DO NOTHINGdoUpdate (upsert)
Update all non-conflict columns when a clash is detected:
await Product.query()
.insert({ sku: "W-001", name: "Widget v2", price: 1099, stock: 80 })
.onConflict(["sku"])
.doUpdate();INSERT INTO products (sku, name, price, stock) VALUES (?, ?, ?, ?)
ON CONFLICT (sku) DO UPDATE SET
name = excluded.name,
price = excluded.price,
stock = excluded.stockdoUpdate with specific columns
Update only specified columns, leaving the rest unchanged:
await Product.query()
.insert({ sku: "G-001", name: "Renamed (ignored)", price: 1799, stock: 0 })
.onConflict(["sku"])
.doUpdate(["price"]); // only price is updated; name stays as-isINSERT INTO products (sku, name, price, stock) VALUES (?, ?, ?, ?)
ON CONFLICT (sku) DO UPDATE SET price = excluded.priceBulk upsert
Combine insertMany with onConflict for price-list style updates:
await Product.query()
.insertMany([
{ sku: "W-001", name: "irrelevant", price: 500, stock: 0 },
{ sku: "Z-001", name: "Zapper", price: 3999, stock: 10 },
])
.onConflict(["sku"])
.doUpdate(["price"]);
// W-001's price is updated to 500; Z-001 is inserted as newINSERT INTO products (sku, name, price, stock) VALUES (?, ?, ?, ?), (?, ?, ?, ?)
ON CONFLICT (sku) DO UPDATE SET price = excluded.priceinsertGraph
insertGraph inserts an entire object graph — related entities and junction rows — in the correct dependency order. You pass a plain object shaped like your entity with its relations nested inside:
manyToOne parent
Provide the parent as a nested object — Typhex inserts it first and wires the foreign key automatically:
const post = await Post.query().insertGraph({
title: "Hello",
author: { name: "Alice" },
});-- 1. Parent first
INSERT INTO users (name) VALUES (?) -- params: ["Alice"]
-- 2. Root with foreign key wired in
INSERT INTO posts (title, authorId) VALUES (?, ?) -- params: ["Hello", 1]oneToMany children
Provide children as an array — Typhex inserts the root first and back-fills the foreign key on each child:
const user = await User.query().insertGraph({
name: "Alice",
posts: [{ title: "First post" }, { title: "Second post" }],
});-- 1. Root first
INSERT INTO users (name) VALUES (?) -- params: ["Alice"]
-- 2. Children batched, with authorId auto-filled
INSERT INTO posts (title, authorId) VALUES (?, ?), (?, ?)
-- params: ["First post", 1, "Second post", 1]manyToMany
Mix new records with references to existing ones using { id: existingId }:
const existingTag = await Tag.query().findById(1);
const post = await Post.query().insertGraph({
title: "Hello",
author: { name: "Alice" },
tags: [
{ id: existingTag.id }, // link existing tag
{ name: "new-tag" }, // insert new tag
],
});INSERT INTO users (name) VALUES (?) -- author
INSERT INTO posts (title, authorId) VALUES (?, ?) -- root post
INSERT INTO tags (name) VALUES (?) -- only the new tag is inserted
-- Junction rows for both existing + new tag
INSERT INTO post_tags (postId, tagId) VALUES (?, ?), (?, ?)Batch insert
Pass an array to insert multiple graphs at once:
const users = await User.query().insertGraph([
{ name: "Alice", posts: [{ title: "A-1" }, { title: "A-2" }] },
{ name: "Bob", posts: [{ title: "B-1" }] },
]);-- Roots batched
INSERT INTO users (name) VALUES (?), (?) -- ["Alice", "Bob"]
-- All children batched, foreign keys back-filled
INSERT INTO posts (title, authorId) VALUES (?, ?), (?, ?), (?, ?)
-- ["A-1", 1, "A-2", 1, "B-1", 2]With a transaction
insertGraph participates in an existing transaction:
await db.transaction(async (trx) => {
await Post.query(trx).insertGraph({
title: "Atomic post",
author: { name: "Dave" },
tags: [{ name: "typescript" }],
});
});If any insert in the graph fails, the transaction rolls back the entire graph.