Managed transaction (auto-callback)

Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to sequelize.transaction.

Notice how the callback passed to transaction returns a promise chain, and does not explicitly call t.commit() nor t.rollback(). If all promises in the returned chain are resolved successfully the transaction is committed. If one or several of the promises are rejected, the transaction is rolled back.

  1. return sequelize.transaction(t => {
  2. // chain all your queries here. make sure you return them.
  3. return User.create({
  4. firstName: 'Abraham',
  5. lastName: 'Lincoln'
  6. }, {transaction: t}).then(user => {
  7. return user.setShooter({
  8. firstName: 'John',
  9. lastName: 'Boothe'
  10. }, {transaction: t});
  11. });
  12. }).then(result => {
  13. // Transaction has been committed
  14. // result is whatever the result of the promise chain returned to the transaction callback
  15. }).catch(err => {
  16. // Transaction has been rolled back
  17. // err is whatever rejected the promise chain returned to the transaction callback
  18. });

Throw errors to rollback

When using the managed transaction you should never commit or rollback the transaction manually. If all queries are successful, but you still want to rollback the transaction (for example because of a validation failure) you should throw an error to break and reject the chain:

  1. return sequelize.transaction(t => {
  2. return User.create({
  3. firstName: 'Abraham',
  4. lastName: 'Lincoln'
  5. }, {transaction: t}).then(user => {
  6. // Woops, the query was successful but we still want to roll back!
  7. throw new Error();
  8. });
  9. });

Automatically pass transactions to all queries

In the examples above, the transaction is still manually passed, by passing { transaction: t } as the second argument. To automatically pass the transaction to all queries you must install the continuation local storage (CLS) module and instantiate a namespace in your own code:

  1. const cls = require('continuation-local-storage');
  2. const namespace = cls.createNamespace('my-very-own-namespace');

To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor:

  1. const Sequelize = require('sequelize');
  2. Sequelize.useCLS(namespace);
  3. new Sequelize(....);

Notice, that the useCLS() method is on the constructor, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances.

CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the transaction property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time:

  1. sequelize.transaction((t1) => {
  2. namespace.get('transaction') === t1; // true
  3. });
  4. sequelize.transaction((t2) => {
  5. namespace.get('transaction') === t2; // true
  6. });

In most case you won't need to access namespace.get('transaction') directly, since all queries will automatically look for a transaction on the namespace:

  1. sequelize.transaction((t1) => {
  2. // With CLS enabled, the user will be created inside the transaction
  3. return User.create({ name: 'Alice' });
  4. });

After you've used Sequelize.useCLS() all promises returned from sequelize will be patched to maintain CLS context. CLS is a complicated subject - more details in the docs for cls-bluebird, the patch used to make bluebird promises work with CLS.

Note: CLS only supports async/await, at the moment, when using cls-hooked package. Although, cls-hooked relies on _experimental APIasync_hooks_