Expo SQLite
expo-sqlite provides a robust interface for local SQLite database persistence within Expo and React Native applications. As of version 55.0.15, it integrates seamlessly with Expo SDK 55. New Expo SDK versions, and consequently updates to expo-sqlite, are typically released three times per year, aligning with the React Native release cadence. This package offers a modern promise-based API for core database operations, including creating tables, inserting, querying, and managing transactions. Its primary differentiator is the streamlined integration into the Expo ecosystem, abstracting away complex native module setup for React Native developers. It is a fundamental tool for building offline-first features and for efficient, structured local data storage, distinguishing itself from simpler key-value stores like `AsyncStorage` when complex queries or relationships are required.
Common errors
-
Error: no such table: MyTable
cause The SQL query references a table name that does not exist or is misspelled in the database schema.fixVerify that the table was correctly created before attempting to query or modify it. Check for typos in the table name in both your `CREATE TABLE` and subsequent `SELECT`/`INSERT`/`UPDATE` statements. -
TypeError: _expo_sqlite__WEBPACK_IMPORTED_MODULE_0__.openDatabase is not a function
cause You are likely using an older import pattern (`import * as SQLite from 'expo-sqlite'; SQLite.openDatabase`) with a newer Expo SDK version that expects direct named exports.fixChange your import to `import { openDatabase } from 'expo-sqlite';` for the callback API or `import { openDatabaseAsync } from 'expo-sqlite';` for the promise API, and use `openDatabase(...)` or `await openDatabaseAsync(...)` directly. -
Error: Database is locked
cause Concurrent write operations are attempting to access the database simultaneously without proper transaction management, or a previous transaction was not properly closed.fixEnsure that all database modifications are wrapped within a single transaction. Avoid opening multiple simultaneous database connections for writes. If using promise-based APIs, ensure `await` is used correctly to prevent concurrent access. -
Error [ERR_REQUIRE_ESM]: require() of ES Module C:\path\to\node_modules\expo-sqlite\build\index.js not supported.
cause Your project is attempting to import `expo-sqlite` using CommonJS `require()` syntax, but `expo-sqlite` is an ES Module (ESM) in recent Expo SDKs.fixMigrate all `require('expo-sqlite')` statements to `import { openDatabase } from 'expo-sqlite';` (or `openDatabaseAsync`). Ensure your build environment (e.g., Metro, Babel) is configured to handle ESM properly.
Warnings
- breaking Starting with Expo SDK 49, the primary API for opening a database changed. You must now import `openDatabase` (or `openDatabaseAsync`/`openDatabaseSync`) directly as a named export. The legacy `import * as SQLite` and then calling `SQLite.openDatabase` is no longer supported and will cause runtime errors.
- gotcha All SQLite operations (e.g., `executeSql`, transactions) are asynchronous. Failing to properly handle promises or callbacks can lead to race conditions, unexpected data states, or errors where operations complete out of order.
- gotcha `expo-sqlite` databases are sandboxed to the application. Database files cannot be directly accessed by other apps or outside the application's secure storage area, and the database will be deleted if the application is uninstalled.
- gotcha Attempting to use `require('expo-sqlite')` will result in an `ERR_REQUIRE_ESM` error in modern Expo SDKs. `expo-sqlite` is published as an ES Module (ESM), and direct CommonJS `require()` is not supported.
Install
-
npm install expo-sqlite -
yarn add expo-sqlite -
pnpm add expo-sqlite
Imports
- openDatabase
import * as SQLite from 'expo-sqlite'; const db = SQLite.openDatabase('name.db');import { openDatabase } from 'expo-sqlite'; - openDatabaseAsync
const { openDatabaseAsync } = require('expo-sqlite');import { openDatabaseAsync } from 'expo-sqlite'; - SQLiteDatabase
import { SQLiteDatabase } from 'expo-sqlite';
Quickstart
import { openDatabase, SQLiteDatabase } from 'expo-sqlite';
import React, { useState, useEffect } from 'react';
import { View, Text, Button, Alert, StyleSheet } from 'react-native';
const db: SQLiteDatabase = openDatabase('my_app_data.db');
export default function App() {
const [items, setItems] = useState<string[]>([]);
useEffect(() => {
db.transaction(tx => {
tx.executeSql(
'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY NOT NULL, text TEXT NOT NULL);',
[],
() => console.log('Table "notes" created or already exists.'),
(_, error) => {
console.error('Error creating table:', error);
return true; // Indicate transaction should rollback on error
}
);
},
(error) => console.error('Transaction error:', error),
() => {
console.log('Database initialization complete.');
fetchNotes();
});
}, []);
const addNote = async () => {
const newNote = `Note ${Date.now()}`;
db.transaction(tx => {
tx.executeSql(
'INSERT INTO notes (text) VALUES (?);',
[newNote],
() => {
Alert.alert('Success', `Added "${newNote}"`);
fetchNotes();
},
(_, error) => {
console.error('Error adding note:', error);
return true;
}
);
});
};
const fetchNotes = () => {
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM notes;',
[],
(_, { rows }) => {
setItems(rows._array.map((item: any) => item.text));
},
(_, error) => {
console.error('Error fetching notes:', error);
return true;
}
);
});
};
return (
<View style={styles.container}>
<Text style={styles.title}>SQLite Notes</Text>
<Button title="Add New Note" onPress={addNote} />
<Button title="Refresh Notes" onPress={fetchNotes} />
<View style={styles.notesContainer}>
{items.length === 0 ? <Text>No notes yet.</Text> : items.map((item, index) => (
<Text key={index} style={styles.noteItem}>- {item}</Text>
))}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 50
},
title: {
fontSize: 24,
marginBottom: 20
},
notesContainer: {
marginTop: 20
},
noteItem: {
marginVertical: 5
}
});