🚀 Using createMany with Model Hooks and Transactions in AdonisJS 5


While working with AdonisJS 5, I ran into an interesting situation that every developer should be aware of when using createMany()
in combination with model hooks.
In short: when using afterCreate
hooks with createMany()
, always pass the transaction to any related model operations inside the hook. Let’s look at a real-world example.
📦 The Scenario
Imagine you're building an e-commerce platform with two models:
Order
– stores customer ordersInvoice
– automatically generated for each order
You want to insert multiple orders using createMany()
and automatically create an Invoice
for each via an afterCreate
hook on the Order
model.
Simple, right? Let’s see what happens.
⚠️ The Catch with createMany()
Here’s how we create multiple orders:
const orders = await Order.createMany(orderPayloads)
AdonisJS internally wraps this operation in a transaction, and for every record created, it triggers the afterCreate
hook.
Inside the afterCreate hook, I tried to create an invoice like this:
@afterCreate()
public static async generateInvoice(order: Order) {
const invoicePayload = {
orderId: order.id,
amount: order.total,
// other fields
}
await Invoice.create(invoicePayload)
}
But when using createMany()
, this started throwing errors — or worse, some invoices weren’t getting created.
🔍 What's Going Wrong?
The problem is that createMany()
wraps all individual inserts in a single transaction, and unless you pass that transaction into any operations inside the hook, your related model calls (like Invoice.create()
) are outside the transaction.
This leads to weird behavior:
If something fails, the order inserts roll back ✅
But the invoices (created outside the transaction) don’t roll back ❌
✅ The Fix: Use order.$trx
Luckily, AdonisJS attaches the current transaction to the model instance under $trx
. You can use it to ensure related inserts participate in the same transaction:
@afterCreate()
public static async generateInvoice(order: Order) {
const invoicePayload = {
orderId: order.id,
amount: order.total,
// other fields
}
let invoice
if (order.$trx?.isTransaction && !order.$trx?.isCompleted) {
invoice = await Invoice.create(invoicePayload, {
client: order.$trx,
})
} else {
invoice = await Invoice.create(invoicePayload)
}
// optional logging or actions
}
Now, when you use:
await Order.createMany(orderPayloads)
Every Invoice
will be created within the same transaction as the respective Order
.
🔄 Why It Matters
This ensures consistency and atomicity:
If anything fails, the entire batch (orders + invoices) is rolled back 💯
You avoid orphaned invoices or inconsistent state
💡 Bonus Tip
If you’re doing additional operations with hooks or service methods, always check if the parent model has a $trx
. You can even extend this pattern for:
Audit logs
Notification queues
Related models
Here’s how you'd manually manage a transaction, too:
const trx = await Database.transaction()
await Order.createMany(orderPayloads, { client: trx })
// possibly other operations
await trx.commit()
But when using hooks — the $trx
shortcut is the best approach.
📝 Final Thoughts
AdonisJS’s createMany()
and lifecycle hooks are powerful tools. But mixing them without handling transactions carefully can introduce bugs and data issues. By checking for $trx
, you can keep your app safe, clean, and reliable — especially during bulk operations.
Have you faced similar issues while working with hooks or transactions? Let’s talk in the comments!
Subscribe to my newsletter
Read articles from Satyam Chouksey directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Satyam Chouksey
Satyam Chouksey
🔧 Professional/Polished: Full Stack Developer | AdonisJS, Vue.js, AWS | Building scalable web apps. Writing code, solving problems — one full stack at a time. JavaScript Enthusiast | Backend & Frontend Dev | Cloud-ready Solutions. 💡 Personal & Friendly: Turning coffee into code & ideas into products ☕💻 I build full stack apps that actually work 😎 Dev by day, debugger by night 🔧 💼 Focused & Tech-Specific: AdonisJS & Vue.js Developer | Cloud & DevOps Explorer Building modern web apps using TypeScript, Node.js & AWS Code + Cloud = ❤️ | Full Stack @ Webledger