✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

BlogIntermediate5 min read

Using transactions with Strapi v4 - with test

September 6, 2023Updated on May 22, 2026

Prerequisites:

Before you jump into this content, you need to have a basic understanding of the following:

  • Basic knowledge of JavaScript
  • Basic understanding of Strapi
  • Basic knowledge of testing and Jest

Introduction

Within our company UFirst Group, we have been using Strapi in two different projects. In one as a CMS and in the other we are using Strapi as a backend for a Next.JS application.

In the latter we use transactions for handling the changes of the data during an import process.

For those who are unfamiliar with the concept of transactions, think of it as creating a point in time that you can go back to if something goes wrong when doing any changes to the database. Here is a definition of transactions from the PostgreSQL:

Transactions are a fundamental concept of all database systems. The essential point of a transaction is that it bundles multiple steps into a single, all-or-nothing operation. The intermediate states between the steps are not visible to other concurrent transactions, and if some failure occurs that prevents the transaction from completing, then none of the steps affect the database at all.

Source: https://www.postgresql.org/docs/8.3/tutorial-transactions.html

This article will show you how we use transactions in Strapi v4 and how to create unit tests for those transactions. But first of all, let’s explain why we needed to use transactions.

Why are we wanted to use transactions

In our project where we use Strapi as a backend, we have to import a lot of data at once (for example: thousands of lines in a csv file). This data is read and the process will either create or update the rows in the database, which will appear as entities in the Strapi admin panel. The frontend will then query these entities through the GraphQL plugin or API endpoints.

When doing the import we don’t want to overwrite the production database directly because if something goes wrong during the import process, we will have some data modified in the database and some data that has not been inserted or updated. This will result in having corrupted data, and the users will see incorrect data (not fully updated).

There are a few solutions to this problem but none are officially documented as of today and we decided to go with transactions. Let’s now see how we use transactions with Strapi v4.

How we use transactions

In the background, Strapi uses Knex.js, a SQL query builder for many types of databases (we are using PostgreSQL in this example) and it supports transactions.

Let’s create a very simple content type for this example. It will be called product and will have two properties (name and price):

content_type.png

Now to the transactions!

We can get the Knex object directly from the global Strapi instance:

/* global strapi */

const knexObject = strapi.db.connection;

And we can create a transaction using this method:

/* global strapi */

const newTransaction = strapi.db.connection.transaction();

With this newTransaction constant, you can perform any changes to the database and be able to commit or rollback at any point.

Here is how to insert a new product using this method:

/* global strapi */

const newTransaction = strapi.db.connection.transaction();
newTransaction("products").insert(
  {
    name: "My new product",
    price: 3.5,
  },
  "*"
);

The new product is now part of the newTransaction, it is not yet present in the database.

We now have the choice to commit (apply the changes) or rollback (go back to the point in time/state before the transaction was started).

Here is how to commit the transaction:

newTransaction.commit();

Here is how to rollback the transaction:

newTransaction.rollback();

Testing

Let’s now test the two cases and find the entity with Strapi:

  • Commit
/* global strapi */

const data = {
  name: "My new Product",
  price: 3.5,
};

it("Inserts a product with a transaction", async () => {
  const newTransaction = await strapi.db.connection.transaction();
  const insertedRows = await newTransaction("products").insert(data, "*");

  expect(insertedRows).toHaveLength(1);

  // Find it with Strapi - still not present in the database
  const productResult = await strapi.entityService.findMany(
    "api::product.product",
    {
      filters: data,
    }
  );

  expect(productResult).toHaveLength(0);

  await newTransaction.commit();

  // Find it with Strapi - found it!
  const productResultAfterCommit = await strapi.entityService.findMany(
    "api::product.product",
    {
      filters: data,
    }
  );

  expect(productResultAfterCommit).toHaveLength(1);

  const newProduct = productResultAfterCommit[0];
  expect(newProduct).toMatchObject(data);

  await strapi.entityService.delete("api::product.product", newProduct.id);
});

commit_test.jpg

In this test you can see that we first create a transaction, then we insert a new row in the transaction. If we search for the entity with Strapi before committing, we do not find it. Then we commit the transaction and we can search for our new row with Strapi’s entity service and sure enough we find it.

  • Rollback
/* global strapi */

const data = {
  name: "My new Product",
  price: 3.5,
};

it("Rollbacks a transaction", async () => {
  const newTransaction = await strapi.db.connection.transaction();
  const insertedRows = await newTransaction("products").insert(data, "*");

  expect(insertedRows).toHaveLength(1);
  await newTransaction.rollback();

  // Make sure the data is not inserted with Strapi
  const productResult = await strapi.entityService.findMany(
    "api::product.product",
    {
      filters: data,
    }
  );

  expect(productResult).toHaveLength(0);
});

rollback_test.png

Here we can see that the insertedRows have a length of one before the transaction rollback. Once we decide to not apply the changes, Strapi will not find the new row, which is exactly what we expect.

Code Source on GitHub: https://github.com/ufirstgroup/strapi-transactions

Conclusion

In this article we saw how to use transactions in Strapi v4 in order to either commit new data or rollback the whole transaction. And both tests show us how they are used and that transactions with Knex work.

Of course you can do more than inserting, Knex has good documentation on how to update, delete, work with relations, etc.

This is a great solution to keep our data uncorrupted and make sure that we don’t disturb the users while manipulating data in the background. In a future article we will explain how we added Typescript support to transactions. The concept will stay the same and it will allow you to have even more control over the data.

Senior Software Engineer & Team Lead at UFirst Group

Demystifying Strapi's Populated & Filtering
BlogIntermediate·27 min read

Demystifying Strapi's Populate & Filtering

How to query, populate, and filter data in Strapi.

·March 9, 2026
Top 5 Best Resource To Learn Next.Js and Strapi.png
BlogBeginner·9 min read

Top 5 Best Resource To Learn Next.Js and Strapi

In this post, we’re highlighting the Top 5 Best Resources to master both Next.js and Strapi. Keeping up with the latest resources to learn and continuously improve your Next.js skills can be overwhelming, whether you’re just starting with Next.js or have been developing apps with Next.js for a while.

·May 5, 2025
How To Migrate Your Project From Strapi 4 to Strapi 5
Blog·20 min read

How To Migrate Your Project From Strapi 4 to Strapi 5

Migrating from Strapi v4 to v5 can seem daunting, but it becomes a manageable process with the right approach and...

·October 1, 2024