Easy transaction management for sequelize

Bram Vandenbon
2 min readFeb 5, 2021

--

The sequelize npm package provides an ORM tool to communicate to SQL databases.

In this post I would like to introduce you to zb-sequelize which strongly simplifies the transaction management.

The Problem

Let’s say we have a fooBar() function which calls a foo() and a bar() function. Both of them are executed within the same transaction.

function fooBar() {
try {
const transaction = sequelize.transaction();
foo(transaction);
bar(transaction);
transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}

The transaction management takes about 7 lines of code in this example.

Now, what would happen if fooBar() would become part of a bigger transaction. Let’s call it start() .

The additional start() function would look as follows.

function start() {
try {
const transaction = sequelize.transaction();
fooBar(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}

But you would also need to change the fooBar() method to accept a transaction parameter.

Assuming that the fooBar() can be called both with or without transaction, things get more tricky.

Alternatively, you could squeeze it in 1 method. It would look as follows. Notice how many if (isLocalTransaction) checks this requires.

function fooBar(transaction?) {
try {
const isLocalTransaction = transaction == null;
if (isLocalTransaction) transaction = sequelize.transaction();
foo(transaction);
bar(transaction);
if (isLocalTransaction) transaction.commit();
} catch(err) {
if (isLocalTransaction) transaction.rollback();
throw err;
}
}

You would perhaps prefer creating 2 methods. One with a transaction parameter, and one without.

The final picture would look as follows.

function start() {
try {
const transaction = sequelize.transaction();
fooBar(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}
function fooBar() {
try {
const transaction = sequelize.transaction();
fooBarTx(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}
function fooBarTx(transaction) {
foo(transaction);
bar(transaction);
}

You do need some kind of convention to separate or distinguish transactional functions from non-transactional functions.

  • You could put the non-transactional methods in a separate class or module.
  • In the example above I used a naming convention to add a Tx extension to the name of the function.

The problem is that all of the above is so verbose. It requires so much repetitive code.

The Solution

I want to show you that there actually is a shorter alternative for this, using 2 decorators.

@Transactional
function fooBar(@Tx transaction) {
foo(transaction);
bar(transaction);
}

Oh, and the start() method would look as follows

@Transactional
function start(@Tx transaction) {
fooBar(transaction);
}

The transaction parameter becomes optional, and will be initialized automatically whenever necessary. It basically allows all methods to be executed individually or as part of a bigger transaction. And you never have to write code to initialize, commit or rollback transactions again.

This requires a dependency:

npm install zb-sequelize

And of course, you have to help zb-sequelize a bit, by showing it where to find your sequelize instance.

import { initSequelizeResolver } from 'zb-sequelize';
initSequelizeResolver((args) => sequelize)

Conclusion

So, how did this solution help us ?
For this simple example, it saved us roughly 20 lines of repetitive code.

Have fun ! :-)

--

--

Bram Vandenbon
Bram Vandenbon

No responses yet