expo-sqlite
Version: 55.0.6
Provides access to a database using SQLite. The database is persisted across restarts of your app. Offers a modern promise-based API with support for transactions, prepared statements, and more.
Installation
npx expo install expo-sqlite
Usage
import * as SQLite from 'expo-sqlite';
// Open database
const db = await SQLite.openDatabaseAsync('mydb.db');
// Create table
await db.execAsync(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
);
`);
// Insert data
await db.runAsync('INSERT INTO users (name, email) VALUES (?, ?)',
'John Doe', 'john@example.com');
// Query data
const users = await db.getAllAsync('SELECT * FROM users');
console.log(users);
API Reference
Database Methods
openDatabaseAsync(databaseName, options)
(databaseName: string, options?: SQLiteOpenOptions) => Promise<SQLiteDatabase>
Opens or creates a SQLite databaseParameters:
databaseName (string): Database file name
options (SQLiteOpenOptions): Optional configuration
const db = await SQLite.openDatabaseAsync('myapp.db');
// With options
const db = await SQLite.openDatabaseAsync('myapp.db', {
useNewConnection: true,
});
openDatabaseSync(databaseName, options)
(databaseName: string, options?: SQLiteOpenOptions) => SQLiteDatabase
Synchronous version of openDatabaseAsyncconst db = SQLite.openDatabaseSync('myapp.db');
deleteDatabaseAsync(databaseName)
(databaseName: string) => Promise<void>
Deletes a database fileawait SQLite.deleteDatabaseAsync('myapp.db');
deleteDatabaseSync(databaseName)
(databaseName: string) => void
Synchronous version of deleteDatabaseAsync
SQLiteDatabase
Query Methods
db.getAllAsync(query, ...params)
(query: string, ...params: any[]) => Promise<any[]>
Executes a query and returns all rowsconst users = await db.getAllAsync('SELECT * FROM users WHERE age > ?', 18);
db.getFirstAsync(query, ...params)
(query: string, ...params: any[]) => Promise<any | null>
Executes a query and returns the first rowconst user = await db.getFirstAsync('SELECT * FROM users WHERE id = ?', 1);
db.runAsync(query, ...params)
(query: string, ...params: any[]) => Promise<SQLiteRunResult>
Executes a query without returning rowsReturns object with lastInsertRowId and changesconst result = await db.runAsync(
'INSERT INTO users (name, email) VALUES (?, ?)',
'Jane Doe',
'jane@example.com'
);
console.log('Inserted ID:', result.lastInsertRowId);
console.log('Rows changed:', result.changes);
db.execAsync(source)
(source: string) => Promise<void>
Executes multiple SQL statementsawait db.execAsync(`
DROP TABLE IF EXISTS users;
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users (name) VALUES ('John');
`);
Prepared Statements
db.prepareAsync(query)
(query: string) => Promise<SQLiteStatement>
Creates a prepared statement for efficient repeated executionconst stmt = await db.prepareAsync('INSERT INTO users (name) VALUES (?)');
for (const name of names) {
await stmt.executeAsync(name);
}
await stmt.finalizeAsync();
Transactions
db.withTransactionAsync(task)
(task: () => Promise<void>) => Promise<void>
Executes operations within a transactionAutomatically commits on success or rolls back on errorawait db.withTransactionAsync(async () => {
await db.runAsync('INSERT INTO users (name) VALUES (?)', 'Alice');
await db.runAsync('INSERT INTO users (name) VALUES (?)', 'Bob');
});
db.withExclusiveTransactionAsync(task)
(task: () => Promise<void>) => Promise<void>
Executes operations within an exclusive transactionGuarantees no other queries interrupt the transactionawait db.withExclusiveTransactionAsync(async () => {
const count = await db.getFirstAsync('SELECT COUNT(*) as count FROM users');
await db.runAsync('INSERT INTO users (name) VALUES (?)', 'New User');
});
Other Methods
Closes the database connection
db.isInTransactionAsync()
Checks if database is currently in a transaction
db.serializeAsync(databaseName)
(databaseName?: string) => Promise<Uint8Array>
Serializes database to Uint8Array
SQLiteStatement
stmt.executeAsync(...params)
(...params: any[]) => Promise<SQLiteExecuteAsyncResult>
Executes the prepared statementconst result = await stmt.executeAsync('John', 'john@example.com');
Finalizes and releases the statementawait stmt.finalizeAsync();
Hooks
Access database from SQLiteProvider contextimport { useSQLiteContext } from 'expo-sqlite';
function MyComponent() {
const db = useSQLiteContext();
// Use db
}
Types
SQLiteRunResult
ID of the last inserted row
Examples
Basic CRUD Operations
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db');
// Create
await db.execAsync(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed INTEGER DEFAULT 0
);
`);
// Insert
const result = await db.runAsync(
'INSERT INTO todos (text) VALUES (?)',
'Learn SQLite'
);
console.log('Inserted todo with ID:', result.lastInsertRowId);
// Read all
const todos = await db.getAllAsync('SELECT * FROM todos');
// Read one
const todo = await db.getFirstAsync('SELECT * FROM todos WHERE id = ?', 1);
// Update
await db.runAsync('UPDATE todos SET completed = 1 WHERE id = ?', 1);
// Delete
await db.runAsync('DELETE FROM todos WHERE id = ?', 1);
Using Prepared Statements
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db');
// Prepare statement for batch inserts
const stmt = await db.prepareAsync(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)'
);
const users = [
['Alice', 'alice@example.com', 25],
['Bob', 'bob@example.com', 30],
['Charlie', 'charlie@example.com', 35],
];
try {
for (const [name, email, age] of users) {
await stmt.executeAsync(name, email, age);
}
} finally {
await stmt.finalizeAsync();
}
console.log('Batch insert complete');
Transactions
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('banking.db');
async function transferFunds(fromId: number, toId: number, amount: number) {
await db.withTransactionAsync(async () => {
// Deduct from sender
await db.runAsync(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
amount,
fromId
);
// Add to receiver
await db.runAsync(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
amount,
toId
);
});
console.log('Transfer completed');
}
Complex Queries
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db');
// Join query
const results = await db.getAllAsync(`
SELECT
users.name,
orders.total,
orders.created_at
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.total > ?
ORDER BY orders.created_at DESC
LIMIT ?
`, 100, 10);
// Aggregation
const stats = await db.getFirstAsync(`
SELECT
COUNT(*) as total_orders,
SUM(total) as revenue,
AVG(total) as avg_order
FROM orders
WHERE created_at > ?
`, Date.now() - 86400000); // Last 24 hours
console.log('Stats:', stats);
React Hook Usage
import { SQLiteProvider, useSQLiteContext } from 'expo-sqlite';
import { useEffect, useState } from 'react';
import { View, Text, FlatList } from 'react-native';
export default function App() {
return (
<SQLiteProvider databaseName="app.db" onInit={initDB}>
<TodoList />
</SQLiteProvider>
);
}
async function initDB(db: SQLite.SQLiteDatabase) {
await db.execAsync(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed INTEGER DEFAULT 0
);
`);
}
function TodoList() {
const db = useSQLiteContext();
const [todos, setTodos] = useState<any[]>([]);
useEffect(() => {
loadTodos();
}, []);
async function loadTodos() {
const result = await db.getAllAsync('SELECT * FROM todos');
setTodos(result);
}
async function addTodo(text: string) {
await db.runAsync('INSERT INTO todos (text) VALUES (?)', text);
await loadTodos();
}
return (
<View>
<FlatList
data={todos}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <Text>{item.text}</Text>}
/>
</View>
);
}
Migration Example
import * as SQLite from 'expo-sqlite';
const CURRENT_VERSION = 2;
async function migrateDatabase() {
const db = await SQLite.openDatabaseAsync('app.db');
let version = 0;
try {
const result = await db.getFirstAsync<{ user_version: number }>(
'PRAGMA user_version'
);
version = result?.user_version ?? 0;
} catch (e) {
// First time
}
if (version === 0) {
// Initial schema
await db.execAsync(`
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
PRAGMA user_version = 1;
`);
version = 1;
}
if (version === 1) {
// Migration to v2
await db.execAsync(`
ALTER TABLE users ADD COLUMN email TEXT;
PRAGMA user_version = 2;
`);
version = 2;
}
console.log('Database migrated to version', version);
return db;
}
| Platform | Supported |
|---|
| iOS | ✅ |
| Android | ✅ |
| Web | ✅ (WebSQL/IndexedDB) |
Best Practices
- Use Prepared Statements: For repeated queries, use
prepareAsync() for better performance
- Transactions: Use transactions for multiple related operations
- Close Connections: Always close database connections when done
- Parameterized Queries: Use
? placeholders to prevent SQL injection
- Error Handling: Wrap database operations in try/catch blocks
- Migrations: Implement version-based migrations for schema changes
Always use parameterized queries with ? placeholders instead of string concatenation to prevent SQL injection attacks.
Resources