sqlite3 vs expo-sqlite vs pouchdb vs realm vs react-native-sqlite-storage
Local Data Persistence Strategies for JavaScript Applications
sqlite3expo-sqlitepouchdbrealmreact-native-sqlite-storageSimilar Packages:

Local Data Persistence Strategies for JavaScript Applications

This comparison evaluates five prominent data storage solutions for JavaScript environments. expo-sqlite provides a managed SQLite interface for Expo applications. pouchdb offers a NoSQL JSON database that syncs with CouchDB. react-native-sqlite-storage is a legacy SQLite binding for React Native. realm is an object database with optional MongoDB synchronization. sqlite3 is a native Node.js binding for SQLite servers and Electron apps.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
sqlite31,372,2726,4083.4 MB1747 months agoBSD-3-Clause
expo-sqlite1,123,92853,01531.1 MB9598 days agoMIT
pouchdb49,60417,6265.53 MB1952 years agoApache-2.0
realm27,2026,000678 MB664a year agoapache-2.0
react-native-sqlite-storage16,2822,826-1855 years agoMIT

Local Data Persistence: SQLite, NoSQL, and Object Stores Compared

Choosing the right storage layer is critical for JavaScript applications that need to work offline or handle large datasets locally. The packages expo-sqlite, pouchdb, react-native-sqlite-storage, realm, and sqlite3 all solve this problem but target different environments and data models. Let's break down how they differ in practice.

🖥️ Environment Compatibility: Where They Run

The first decision is where your code runs. Some packages are built for mobile, others for servers, and some for both.

expo-sqlite is designed specifically for the Expo managed workflow.

  • It works on iOS and Android through Expo modules.
  • It does not require ejecting to bare workflow.
// expo-sqlite: Opening a database
import { useExpoSQLiteDatabase } from 'expo-sqlite';

const db = useExpoSQLiteDatabase('my.db');

pouchdb runs in browsers, Node.js, and React Native.

  • It uses adapters to switch storage backends (e.g., IndexedDB, SQLite).
  • Great for web apps that need mobile parity.
// pouchdb: Creating a database instance
import PouchDB from 'pouchdb';

const db = new PouchDB('my_database');

react-native-sqlite-storage targets bare React Native projects.

  • It requires linking native iOS and Android code.
  • Often struggles with new React Native architecture updates.
// react-native-sqlite-storage: Opening a database
import SQLite from 'react-native-sqlite-storage';

const db = SQLite.openDatabase({ name: 'my.db' });

realm works in React Native, Node.js, and Electron.

  • It uses its own native engine instead of SQLite.
  • Requires adding native dependencies to your project.
// realm: Opening a Realm
import Realm from 'realm';

const realm = new Realm({ schema: [PersonSchema] });

sqlite3 is strictly for Node.js and Electron.

  • It relies on native C++ bindings compiled with node-gyp.
  • Cannot run in a standard React Native JavaScript bridge.
// sqlite3: Opening a database in Node
import sqlite3 from 'sqlite3';

const db = new sqlite3.Database('my.db');

🗃️ Data Model: SQL vs JSON vs Objects

How you structure data changes significantly between these tools. Some use tables, others use documents or objects.

expo-sqlite uses standard relational tables.

  • You define schemas with CREATE TABLE statements.
  • Best for structured data with relationships.
// expo-sqlite: Creating a table
await db.execAsync(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY NOT NULL,
    name TEXT
  );
`);

pouchdb stores JSON documents.

  • No fixed schema required.
  • Each document has a unique _id.
// pouchdb: Saving a document
await db.put({
  _id: 'user1',
  name: 'Alice',
  role: 'admin'
});

react-native-sqlite-storage also uses relational tables.

  • Syntax matches standard SQLite.
  • Similar structure to expo-sqlite but different API.
// react-native-sqlite-storage: Creating a table
db.transaction(tx => {
  tx.executeSql(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY NOT NULL,
      name TEXT
    );
  `);
});

realm uses live objects based on schemas.

  • You define classes or schema objects.
  • Results update automatically when data changes.
// realm: Defining a schema
const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string',
    age: 'int'
  }
};

sqlite3 uses standard relational tables.

  • Identical SQL syntax to expo-sqlite.
  • Used primarily in server-side scripts.
// sqlite3: Creating a table
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT
  )
`);

🔍 Querying Data: Syntax and Experience

Reading data is where you spend most of your time. The experience ranges from raw SQL to object queries.

expo-sqlite uses async/await with SQL strings.

  • Modern API feels like standard JavaScript.
  • Returns rows as arrays or objects.
// expo-sqlite: Querying rows
const result = await db.getFirstAsync('SELECT * FROM users WHERE id = ?', [1]);

pouchdb uses map-reduce or Mango queries.

  • Query by JSON selectors.
  • Requires indexing for complex queries.
// pouchdb: Finding documents
const result = await db.find({
  selector: { name: 'Alice' }
});

react-native-sqlite-storage uses callback-based transactions.

  • Older style requires nested functions.
  • More verbose than modern async APIs.
// react-native-sqlite-storage: Querying rows
db.transaction(tx => {
  tx.executeSql('SELECT * FROM users', [], (tx, results) => {
    console.log(results.rows.item(0));
  });
});

realm uses object queries with filters.

  • No SQL strings needed.
  • Type-safe if using TypeScript.
// realm: Querying objects
const people = realm.objects('Person').filtered('age > 18');

sqlite3 uses callbacks or promises via wrappers.

  • Native API is callback-based.
  • Often wrapped by libraries like better-sqlite3 for sync usage.
// sqlite3: Querying rows
db.all('SELECT * FROM users', [], (err, rows) => {
  console.log(rows);
});

⚠️ Maintenance and Risk Assessment

Long-term support matters when your app grows. Some packages are stable, while others are fading.

expo-sqlite is actively maintained by the Expo team.

  • Updates align with Expo SDK releases.
  • Low risk for Expo users.

pouchdb has a long history and stable community.

  • Maintenance is steady but slower than before.
  • Still reliable for sync use cases.

react-native-sqlite-storage is effectively legacy.

  • Updates are infrequent.
  • High risk of breaking with new React Native versions.
  • Do not start new projects with this.

realm is backed by MongoDB.

  • Active development with cloud sync features.
  • License changes have occurred, so check terms.

sqlite3 is the standard for Node.js.

  • Very stable for server environments.
  • Compilation issues can arise on different OS versions.

📊 Summary: Key Differences

Featureexpo-sqlitepouchdbreact-native-sqlite-storagerealmsqlite3
Primary EnvExpo MobileWeb / Mobile / NodeBare React NativeMobile / NodeNode / Electron
Data ModelSQL TablesJSON DocumentsSQL TablesObjectsSQL Tables
SyncManualBuilt-in CouchDBManualMongoDB AtlasManual
API StyleAsync/AwaitPromiseCallbackLive ObjectsCallback
Status✅ Active✅ Active❌ Legacy✅ Active✅ Active

💡 The Big Picture

expo-sqlite is the default choice for Expo developers. It removes native complexity while giving you full SQL power. Stick with this if you are in the managed workflow.

pouchdb shines when synchronization is the main goal. If you need to sync local data with a remote CouchDB instance without writing custom API endpoints, this is the tool.

react-native-sqlite-storage should be avoided in new work. It lacks the modern ergonomics and stability of expo-sqlite or realm. Migrating away from it is common in mature codebases.

realm is best for complex object graphs. If your data looks more like connected objects than rows in a table, Realm reduces boilerplate. The optional cloud sync is a major bonus for some teams.

sqlite3 belongs on the server or desktop. Use it for Node.js CLI tools or Electron apps. Do not try to force it into a mobile React Native build unless you have a very specific native module setup.

Final Thought: Match the database to your environment first. If you are on Expo, use expo-sqlite. If you need sync, look at pouchdb or realm. If you are on Node.js server, sqlite3 is the standard. Avoid legacy packages unless maintaining old code.

How to Choose: sqlite3 vs expo-sqlite vs pouchdb vs realm vs react-native-sqlite-storage

  • sqlite3:

    Choose sqlite3 if you are building a Node.js command-line tool or an Electron desktop app. It does not work in standard React Native or browser environments. It requires native compilation via node-gyp. It is the standard for server-side SQLite usage.

  • expo-sqlite:

    Choose expo-sqlite if you are building an app with the Expo managed workflow. It offers the best stability and ease of setup within that ecosystem. You get standard SQL power without handling native code. It is the safest bet for new Expo projects.

  • pouchdb:

    Choose pouchdb if your app needs to sync data with a CouchDB server or requires a NoSQL JSON document store. It works well in browsers and React Native via adapters. It is ideal for offline-first applications that need bidirectional sync.

  • realm:

    Choose realm if you prefer an object-oriented database over SQL tables. It is a strong choice if you plan to use MongoDB Atlas Device Sync. It handles complex relationships well without writing SQL queries. Performance is generally high for large datasets.

  • react-native-sqlite-storage:

    Avoid react-native-sqlite-storage for new projects. It is considered legacy and often breaks with newer React Native versions. Maintenance is sporadic compared to modern alternatives. Only use it if you are maintaining an old app that already depends on it.

README for sqlite3

:no_entry: [DEPRECATED] node-sqlite3

Note: This repository is currently unmaintained. We will not update any of its issues or pull requests.


Asynchronous, non-blocking SQLite3 bindings for Node.js.

Latest release Build Status FOSSA Status N-API v3 Badge N-API v6 Badge

Features

Installing

You can use npm or yarn to install sqlite3:

  • (recommended) Latest published package:
npm install sqlite3
# or
yarn add sqlite3
  • GitHub's master branch: npm install https://github.com/tryghost/node-sqlite3/tarball/master

Prebuilt binaries

sqlite3 v5+ was rewritten to use Node-API so prebuilt binaries do not need to be built for specific Node versions. sqlite3 currently builds for both Node-API v3 and v6. Check the Node-API version matrix to ensure your Node version supports one of these. The prebuilt binaries should be supported on Node v20.17.0+.

The module uses prebuild-install to download the prebuilt binary for your platform, if it exists. These binaries are hosted on GitHub Releases for sqlite3 versions above 5.0.2, and they are hosted on S3 otherwise. The following targets are currently provided:

  • darwin-arm64
  • darwin-x64
  • linux-arm64
  • linux-x64
  • linuxmusl-arm64
  • linuxmusl-x64
  • win32-x64

Unfortunately, prebuild cannot differentiate between armv6 and armv7, and instead uses arm as the {arch}. Until that is fixed, you will still need to install sqlite3 from source.

Support for other platforms and architectures may be added in the future if CI supports building on them.

If your environment isn't supported, it'll use node-gyp to build SQLite, but you will need to install a C++ compiler and linker.

Other ways to install

It is also possible to make your own build of sqlite3 from its source instead of its npm package (See below.).

The sqlite3 module also works with node-webkit if node-webkit contains a supported version of Node.js engine. (See below.)

SQLite's SQLCipher extension is also supported. (See below.)

API

See the API documentation in the wiki.

Usage

Note: the module must be installed before use.

const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');

db.serialize(() => {
    db.run("CREATE TABLE lorem (info TEXT)");

    const stmt = db.prepare("INSERT INTO lorem VALUES (?)");
    for (let i = 0; i < 10; i++) {
        stmt.run("Ipsum " + i);
    }
    stmt.finalize();

    db.each("SELECT rowid AS id, info FROM lorem", (err, row) => {
        console.log(row.id + ": " + row.info);
    });
});

db.close();

Source install

To skip searching for pre-compiled binaries, and force a build from source, use

npm install --build-from-source

The sqlite3 module depends only on libsqlite3. However, by default, an internal/bundled copy of sqlite will be built and statically linked, so an externally installed sqlite3 is not required.

If you wish to install against an external sqlite then you need to pass the --sqlite argument to npm wrapper:

npm install --build-from-source --sqlite=/usr/local

If building against an external sqlite3 make sure to have the development headers available. Mac OS X ships with these by default. If you don't have them installed, install the -dev package with your package manager, e.g. apt-get install libsqlite3-dev for Debian/Ubuntu. Make sure that you have at least libsqlite3 >= 3.6.

Note, if building against homebrew-installed sqlite on OS X you can do:

npm install --build-from-source --sqlite=/usr/local/opt/sqlite/

Custom file header (magic)

The default sqlite file header is "SQLite format 3". You can specify a different magic, though this will make standard tools and libraries unable to work with your files.

npm install --build-from-source --sqlite_magic="MyCustomMagic15"

Note that the magic must be exactly 15 characters long (16 bytes including null terminator).

Building for node-webkit

Because of ABI differences, sqlite3 must be built in a custom to be used with node-webkit.

To build sqlite3 for node-webkit:

  1. Install nw-gyp globally: npm install nw-gyp -g (unless already installed)

  2. Build the module with the custom flags of --runtime, --target_arch, and --target:

NODE_WEBKIT_VERSION="0.8.6" # see latest version at https://github.com/rogerwang/node-webkit#downloads
npm install sqlite3 --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION)

You can also run this command from within a sqlite3 checkout:

npm install --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION)

Remember the following:

  • You must provide the right --target_arch flag. ia32 is needed to target 32bit node-webkit builds, while x64 will target 64bit node-webkit builds (if available for your platform).

  • After the sqlite3 package is built for node-webkit it cannot run in the vanilla Node.js (and vice versa).

    • For example, npm test of the node-webkit's package would fail.

Visit the “Using Node modules” article in the node-webkit's wiki for more details.

Building for SQLCipher

For instructions on building SQLCipher, see Building SQLCipher for Node.js. Alternatively, you can install it with your local package manager.

To run against SQLCipher, you need to compile sqlite3 from source by passing build options like:

npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/

node -e 'require("sqlite3")'

If your SQLCipher is installed in a custom location (if you compiled and installed it yourself), you'll need to set some environment variables:

On OS X with Homebrew

Set the location where brew installed it:

export LDFLAGS="-L`brew --prefix`/opt/sqlcipher/lib"
export CPPFLAGS="-I`brew --prefix`/opt/sqlcipher/include/sqlcipher"
npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix`

node -e 'require("sqlite3")'

On most Linuxes (including Raspberry Pi)

Set the location where make installed it:

export LDFLAGS="-L/usr/local/lib"
export CPPFLAGS="-I/usr/local/include -I/usr/local/include/sqlcipher"
export CXXFLAGS="$CPPFLAGS"
npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/local --verbose

node -e 'require("sqlite3")'

Custom builds and Electron

Running sqlite3 through electron-rebuild does not preserve the SQLCipher extension, so some additional flags are needed to make this build Electron compatible. Your npm install sqlite3 --build-from-source command needs these additional flags (be sure to replace the target version with the current Electron version you are working with):

--runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers

In the case of MacOS with Homebrew, the command should look like the following:

npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers

Testing

npm test

Contributors

Acknowledgments

Thanks to Orlando Vazquez, Eric Fredricksen and Ryan Dahl for their SQLite bindings for node, and to mraleph on Freenode's #v8 for answering questions.

This module was originally created by Mapbox & is now maintained by Ghost.

Changelog

We use GitHub releases for notes on the latest versions. See CHANGELOG.md in git history for details on older versions.

Copyright & license

Copyright (c) 2013-2026 Mapbox & Ghost Foundation

node-sqlite3 is BSD licensed.

FOSSA Status