React Native Quick Sqlite with TypeORM integration and performance comparison

Use of the JSI solution and comparison to the old architecture using bridges.

Lukasz Kurant
6 min readJun 30, 2023
Photo by James Barr on Unsplash

Among the ways to store more data in a React Native application, we have many different libraries tailored to our needs. Among the classic Async Storage (along with newer equivalents using the new architecture, like react-native-mmkv-store) we have plenty of solutions such as Sqlite, RealmDB, WatermelonDB or PouchDB. However, let’s keep in mind that due to the change in architecture, we have much more efficient counterpart libraries, enabling much accelerated communication between the native part and Javascript code.

One such library is react-native-quick-sqlite, which provides the latest Sqlite database, along with a JSI-based architecture that allows communication without the use of native bridges. The creators of the library, for reasons I don’t understand, intentionally don’t present performance results between the two architectures, citing only similar studies in the case of the PouchDB database (which, admittedly, uses the provided library, but adds a different layer of abstraction), (article available here), where the performance increase is of the order of 2~5x, however, due to the creation of indexes and wrappers, this is not a reliable study in my opinion, although the performance acceleration is noticeable and indisputable. According to the authors, noticeable changes will be visible only with more data sent to the database, which I will also try to check.

In this post, I would like to show a practical application of the React Native Quick Sqlite library, along with direct integration with the TypeORM library, adding another layer of abstraction and enabling friendlier database operations. We will also compare the given solutions with the previous architecture and react-native-sqlite-storage library checking the actual performance difference.

Project preparation

At first, we need to prepare a basic project by executing the command:

npx react-native init quicksqlite

After installing all the dependencies, we have a working React Native application. In my case, in version 0.72. In my experiments, I will use the TypeORM library and check the performance on a clean example. Therefore, I install the given dependencies:

yarn add react-native-quick-sqlite typeorm reflect-metadata

You can read more about the TypeORM library dependencies here: https://github.com/typeorm/typeorm

To enable TypeORM integration with Quick Sqlite, we need to add some changes to the package.json file of the TypeORM library. To do this, let’s install a library to add changes to other libraries:

yarn add --dev patch-package

Next, modify the node_modules/typeorm/package.json file:

// ...
"exports": {
"./package.json": "./package.json", // add this
".": {
"types": "./index.d.ts",
// ...

Then run the following command so that you don’t lose the above change on subsequent package installations:

yarn patch-package --exclude 'nothing' typeorm

Next, we need to rename the library using the babel plugin, which we install with the command (and another library that enables the use of decorators):

yarn add babel-plugin-module-resolver @babel/plugin-proposal-decorators -dev

Then, in the babel.config.js file in the plugins section, add an entry:

// ...
['@babel/plugin-proposal-decorators', {legacy: true}],
[
'module-resolver',
{
alias: {
"react-native-sqlite-storage": "react-native-quick-sqlite"
},
},
],
// ...

To properly configure TypeORM, in the index.js file in the project’s root directory, we need to add:

import 'reflect-metadata';

Next, in the tsconfig.json file under compilerOptions, we need to add support for decorators, useful for creating models:

"strict": false,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"skipLibCheck": true

Let’s also remember to install the necessary iOS packages by running the command:

npx pod-install

Database connection

To begin with, we need to connect to the database. We create a new connection as follows. The Person class shown below is given here as the entity.

import {typeORMDriver} from 'react-native-quick-sqlite';
import {DataSource} from 'typeorm';
import {Person} from './Person';

export const dataSource = new DataSource({
database: 'quicksqlitetest-typeorm.db',
entities: [Person],
location: '.',
logging: [],
synchronize: true,
type: 'react-native',
driver: typeORMDriver,
});

Then, anywhere in our application, we need to execute the initialize() method to establish a connection (and create a base if one does not exist).

useEffect(() => {
const connect = async () => {
await dataSource.initialize();
};

connect();
}, []);

Model

For the purpose of the presentation, we will create a simple Person model containing several fields of different types, where id will be the primary key with an automatically generated value.

import {Entity, Column, BaseEntity, PrimaryGeneratedColumn} from 'typeorm';

@Entity('people')
export class Person extends BaseEntity {
@PrimaryGeneratedColumn() id: number;
@Column('datetime') createdAt: Date;
@Column('text') firstName: string;
@Column('text') lastName: string;
@Column('text') age: number;
}

Simple operations

To perform simple operations on an entity, it is necessary to create a so-called repository:

export const PersonRepository = dataSource.getRepository(Person);

To add a new object we can use:

const person = PersonRepository.create({
createdAt: new Date(),
firstName: `Person 1`,
lastName: `Person Lastname 1`,
age: 10,
}),

await person.save();

To search for items:

  const peopleSelected = await PersonRepository.find({});

To edit:

await PersonRepository.update({}, {lastName: 'test'});

And remove:

 await PersonRepository.delete({});

However, you can read about this directly in the TypeORM library documentation, and I would not want to discuss it in detail here.

Performance

However, let’s check how it looks in terms of performance and compare the following methods:

  • react-native-quick-sqlite library + TypeORM (JSI solution)
  • react-native-sqlite-storage library + TypeORM (old solution — bridges architecture)

In this case, the use of TypeOrm allows us to examine the performance of the libraries on a real example, where the comfort of the programmer also affects the final product.

In both cases, the following tests were conducted:

  • Adding 1, 10 and 10000 records to the database.
  • Modifying 1, 10 and 10000 records from the base.
  • Selecting 1, 10 and 10000 records from the base.
  • Deleting 1, 10 and 10000 records from the base.

Each test was repeated 5 times and the arithmetic average was calculated from these values.

The tests were conducted on the following devices:

  • iPhone 11 (iOS 16.5)
  • Google Pixel 4 (Android 12)

The results are as follows. The time in ms has been marked on the Y axis.

Select comparison
Create comparison
Update comparison
Delete comparison

Summary

As could be predicted in the case of modification and deletion operations, there is no reasonable difference between execution times — all operations are performed directly by the database and there is no need to transfer data between the native thread and the Javascript thread. In the case of select and add, additionally, there is time required to send all data directly to the Sqlite database. In the case of the JSI architecture, everything runs much more smoothly (in the case of the bridge architecture, the bridge itself is the bottleneck that causes the greatest performance degradation). This has an impact when sending large amounts of data, and using a library with JSI in such cases will have a significant impact on application performance.

According to what the library’s creators say, the actual acceleration especially for large data sets transferred to the database ranges from 1.1 to as much as 6 times.

The code itself needed to perform the experiments can be found here: https://github.com/lukaszkurantdev/blog-quick-sqlite-comparison

--

--

Lukasz Kurant

Fullstack Developer interested in solving difficult problems.