React Native guide

Use queries, transactions, extensions, backups, and lifecycle in a mobile application.

These recipes use an open db from the React Native quickstart. Keep database ownership in an application service; components call that service.

Query application data

await db.execute('CREATE TABLE IF NOT EXISTS notes (body text NOT NULL)');
await db.execute('INSERT INTO notes (body) VALUES ($1)', ['First note']);
const result = await db.query('SELECT body FROM notes');
console.log(result.rows);

Use $1 parameters for user-supplied values. query returns decoded rows; execute returns command metadata. Use rowMode: 'array' when a query has duplicate column names.

Run a transaction

await db.transaction(async (tx) => {
  await tx.execute('INSERT INTO notes (body) VALUES ($1)', ['First']);
  await tx.execute('INSERT INTO notes (body) VALUES ($1)', ['Second']);
});

Returning commits; throwing rolls back. All callback queries must use tx. Do not send manual transaction-lifecycle SQL or call the outer db. Use tx.rollback() for explicit rollback; savepoints are supported.

Add an extension

Install the native extension package, then select it in the Expo plugin configuration before building:

npm install @oliphaunt/extension-vector@0.3.0
{
  "expo": {
    "plugins": [["@oliphaunt/react-native", { "extensions": ["vector"] }]]
  }
}

Rebuild the native app, then import its descriptor when opening:

import vector from '@oliphaunt/extension-vector';

const db = await Oliphaunt.open({
  storage: { kind: 'applicationData', name: 'main' },
  extensions: [vector],
});
await db.execute('CREATE EXTENSION IF NOT EXISTS vector');

The plugin packages selected extension resources and dependencies. Extension changes require a native rebuild; a JavaScript update alone cannot add them. For ICU collations, replace the standard seed dependency with @oliphaunt/seed-native-ios-datum64-icu and install @oliphaunt/icu; use matching database-resource versions.

Choose process isolation

The quickstart uses direct mode. For a separate database process, follow mobile broker setup. Broker mode requires its own storage and restore path; use the recipes below for direct mode.

Back up and restore

Direct mode stays bound to one database root and configuration for the lifetime of the application process. Closing a handle does not let that process switch to another root. Prepare the restored data, then use it on the next launch.

const archive = await db.backup();
await db.close();
await Oliphaunt.restore(
  { kind: 'applicationData', name: 'restored' },
  archive,
);

On a subsequent application launch, open the restored destination:

const restored = await Oliphaunt.open({
  storage: { kind: 'applicationData', name: 'restored' },
});
await restored.close();

Restore requires new or empty persistent storage. It cannot target temporary storage. Reapply required extensions when opening the restored database. Keep physical restores within a compatible native runtime family.

Handle lifecycle and errors

Call db.cancel() to request interruption of active SQL, and await the query's outcome. Cancelling a JavaScript promise does not interrupt PostgreSQL.

Await db.close() when your application finishes using the database. It stops new work and drains accepted operations. Garbage collection and native module invalidation provide fallback cleanup, but they cannot report shutdown errors to your app.

A SQL failure is a PostgresError with SQLSTATE. Transaction failures roll back when possible; an AggregateError preserves the callback and rollback/database failures when both matter. After a terminal protocol error, close and reopen persistent storage rather than reusing the handle.

Troubleshooting

SymptomCheck
Native module is unavailableNew Architecture enabled and a native build installed
Works in development, fails in the appRuntime resources and extensions are packaged for that platform
Changes disappear after restartUse applicationData or directory, not temporary storage
A transaction waitsUse only tx for its SQL
Extension changes have no effectRebuild the native application

See Ship a mobile database for backgrounding and recovery guidance.