Unit of Work vs Transactions in ORMs — Comparing TypeORM, Sequelize, Prisma, MikroORM, and Drizzle
When working with databases in software applications, one key pattern that helps keep data consistent is transactions and the Unit of Work…
Unit of Work vs Transactions in ORMs — Comparing TypeORM, Sequelize, Prisma, MikroORM, and Drizzle

When working with databases in software applications, one key pattern that helps keep data consistent is transactions and the Unit of Work. This pattern is especially useful whenever multiple related changes need to happen together.
In this article, we’ll explain what the Unit of Work is, why it matters, and how it’s implemented in popular ORMs like TypeORM, Sequelize, Prisma, MikroORM, and Drizzle.
What is Unit of Work?
The Unit of Work pattern tracks changes made to entities in memory, groups them together, and then commits them as a single atomic operation. If everything succeeds, all changes are saved; if any operation fails, all changes can be discarded.
In other words, the Unit of Work acts like a change manager: it keeps track of which entities have been created, updated, or deleted, and ensures they’re all persisted consistently.
Why is Unit of Work useful?
- Consistency: Multiple related changes either all succeed or all fail.
- Simpler code: You don’t have to manually manage multiple transactions.
- Separation of concerns: Business logic does not need to worry about database commits or rollbacks.
Unit of Work vs Transaction
It’s important to note that transactions are not the same as a Unit of Work.
- Transaction: ensures multiple operations in the database succeed or fail together (ACID principle).
- Unit of Work: tracks entity changes in memory and decides when and how to flush them to the database.
Many ORMs provide transaction support, but only a few include a real Unit of Work mechanism (with in-memory change tracking and an identity map).
Explicit Transactions vs EntityManager-based Transactions
When using an ORM, there are generally two ways to handle transactions:
Explicit Transactions
You manually control the transaction: start it, commit it, and roll it back if needed. For example, at a grocery store, you scan each item yourself and press “pay” manually. If one item won’t scan, you cancel everything — you’re in full control.
Example (Sequelize):
const transaction = await sequelize.transaction();
try {
await User.create({ name: "Sara" }, { transaction });
await Order.create({ userId: 1 }, { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
}
EntityManager-based Transactions
Here, the ORM manages the transaction for you. You tell it: “Do these operations together,” and it automatically commits or rolls back as needed. Like a cashier scanning all your items for you — you just place them on the counter.
Example (TypeORM):
await dataSource.manager.transaction(async em => {
const user = em.create(User, { name: "Sara" });
const order = em.create(Order, { user });
await em.save(user);
await em.save(order);
});
Both achieve the same end goal : all-or-nothing changes, but the control flow differs.
How Do Popular ORMs Handle Transactions and Unit of Work?
1. TypeORM
TypeORM supports both explicit transactions and EntityManager-based transactions.
Explicit:
const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
try {
await queryRunner.manager.decrement(Account, { id: 1 }, "balance", 100);
await queryRunner.manager.increment(Account, { id: 2 }, "balance", 100);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
EntityManager-based:
await dataSource.manager.transaction(async em => {
const user = em.create(User, { name: "Sara" });
const order = em.create(Order, { user });
await em.save(user);
await em.save(order);
});
TypeORM offers a limited Unit of Work through its EntityManager — it can track entities created or modified during a transaction, but it doesn’t have a full identity map or deep change tracking like more advanced ORMs.
2. Prisma
Prisma focuses on simplicity and safety. It provides transactional execution via $transaction, but it doesn’t track in-memory changes between queries.
await prisma.$transaction(async (prisma) => {
await prisma.account.update({ where: { id: 1 }, data: { balance: { decrement: 100 } } });
await prisma.account.update({ where: { id: 2 }, data: { balance: { increment: 100 } } });
});
Prisma ensures atomicity but does not implement a Unit of Work — it only groups queries in a transaction.
3. Sequelize
Sequelize supports explicit transactions similar to TypeORM but does not maintain entity state in memory.
const transaction = await sequelize.transaction();
try {
await User.create({ name: "Sara" }, { transaction });
await Order.create({ userId: 1 }, { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
}
It provides reliable transactional support but no real Unit of Work or identity map.
4. MikroORM
MikroORM is a TypeScript ORM designed with the Unit of Work pattern at its core.
const em = orm.em.fork();
const user = em.create(User, { name: "Sara" });
const order = em.create(Order, { user });
await em.flush();
Here, MikroORM tracks all entities and their state changes in memory.
When flush() is called, it figures out what needs to be inserted, updated, or deleted, and performs all changes in a single transaction.
If something fails, you can clear the context (em.clear()) to discard pending changes.
So this is a true Unit of Work implementation :D
5. Drizzle ORM
Drizzle focuses on simplicity and type safety. It provides transactional support but no entity tracking layer.
await db.transaction(async (tx) => {
await tx.account.update({ where: { id: 1 }, data: { balance: { decrement: 100 } } });
await tx.account.update({ where: { id: 2 }, data: { balance: { increment: 100 } } });
});
Drizzle ensures atomic commits, but all operations are query-based, no in-memory Unit of Work.
WELL WELL WELL, Wait…
They all seem to do the same thing, right? (Except MikroORM, which is actually built for this job 😁)
I know, they look similar because they’re all good citizens of the ACID world: everything happens or nothing does. But the real difference lies in how much intelligence the ORM has between your entities and the database.
The Grocery Store Analogy
- TypeORM (Explicit): You scan every item and press “pay” yourself. Full control, but easy to mess up.
- TypeORM (EntityManager) / Prisma / Drizzle: The cashier scans all your items and handles the payment. You just stand there — safe and automatic.
- MikroORM: You can add, remove, or modify items in your cart freely, and only when you’re ready, you hit “pay.” That’s the Unit of Work in action.
- Sequelize: You write several checks manually. Forget one, and something breaks.
And Finally …
All of these ORMs can manage transactions and maintain ACID consistency. But if you need a true Unit of Work, one that tracks in-memory changes and intelligently flushes them to the database, the clear winner in the JavaScript/TypeScript ecosystem is MikroORM.
Thanks for reading ❤️ I hope the stuff I shared is useful. If you spot any mistakes or something’s unclear, please let me know, I’d really appreciate the feedback :)
메타데이터
- post_id
- da42c2efc07d
- slug
- unit-of-work-vs-transactions-in-orms-comparing-typeorm-sequelize-prisma-mikroorm-and-drizzle-da42c2efc07d
- url
- https://medium.com/@saragholizadeh1999/unit-of-work-vs-transactions-in-orms-comparing-typeorm-sequelize-prisma-mikroorm-and-drizzle-da42c2efc07d
- canonical_url
- https://medium.com/@saragholizadeh1999/unit-of-work-vs-transactions-in-orms-comparing-typeorm-sequelize-prisma-mikroorm-and-drizzle-da42c2efc07d
- author_url
- https://medium.com/@saragholizadeh1999
- status
- ok
- fetched_at
- 2026-09-18 20:22:38