Drivers
ElectricSQL works with existing SQLite database driver libraries. The principle is that you import and use your existing driver as normal, after “electrifying” it by passing the original driver instance to an electrify
function provided by the ElectricSQL client library.
This guide has instructions for integrating with drivers for the following platforms. Note that if we don’t currently support your platform, you can implement your own generic driver integration.
Mobile
We currently support JavaScript based mobile applications. Let us know on Discord if you’re interested in using ElectricSQL with other platforms.
Cordova
We support Apache Cordova via the cordova-sqlite-storage driver. Make sure you’ve bundled your migrations, instantiate your driver as normal and then electrify it:
import { electrify } from 'electric-sql/cordova'
import config from '.electric/@config'
document.addEventListener('deviceready', () => {
window.sqlitePlugin.openDatabase(opts, async (original) => {
const db = await electrify(original, config)
// Use as normal, e.g.:
db.executeSql('select foo from bar', [], (results) => {
console.log('query results: ', results)
})
})
})
Expo
We support Expo via expo-sqlite. Make sure you’ve bundled your migrations, instantiate your driver as normal and then electrify it:
import * as SQLite from 'expo-sqlite'
import { electrify } from 'electric-sql/expo'
import config from '.electric/@config'
SQLite.openDatabase('example.db', null, null, null, async (original) => {
const db = await electrify(original, config)
// Use as normal, e.g.:
db.transaction((tx) => {
tx.executeSql('select foo from bar', [], (_tx, results) => {
console.log('query results: ', results)
})
})
})
See the Expo example application for more information.
React Native
We support React Native via the react-native-sqlite-storage driver. Make sure you’ve bundled your migrations, instantiate your driver as normal and then electrify it:
import SQLite from 'react-native-sqlite-storage'
import { electrify } from 'electric-sql/react-native'
import config from '.electric/@config'
// You can use the driver with or without the promise API enabled.
// Either way, you need to pass in a flag to the `electrify` function
// below to indicate whether promises are enabled or not.
const promisesEnabled = true
SQLite.enablePromise(promisesEnabled)
SQLite.openDatabase('example.db')
.then(original => electrify(original, promisesEnabled, config))
.then(db => { // Use as normal, e.g.:
db.transaction((tx) => {
tx.executeSql('SELECT 1', [], (_tx, results) => {
console.log('query results: ', results)
})
})
})
See the React Native example application for more information.
Web
ElectricSQL supports running in the browser using SQL.js with absurd-sql and the new wa-sqlite WASM build. We recommend using wa-sqlite
moving forward.
SQL.js
ElectricSQL runs SQL.js with James Long’s absurd-sql system for persistence. This runs in a web worker (which we also use to keep background replication off the main thread). As a result, the electrified db client provides an asynchronous version of a subset of the SQL.js driver interface.
First create a worker.js
file that imports and starts an ElectricWorker process:
// worker.js
import { ElectricWorker } from 'electric-sql/browser'
ElectricWorker.start(self)
Then, in your main application (noting that you pass your config into the
SQL.openDatabase
call):
import { initElectricSqlJs } from 'electric-sql/browser'
import config from '.electric/@config'
// Start the background worker.
const url = new URL('./worker.js', import.meta.url)
const worker = new Worker(url, {type: "module"})
// Electrify the SQL.js / absurd-sql machinery and then open
// a persistent, named database.
initElectricSqlJs(worker, {locateFile: file => `/${file}`})
.then(SQL => SQL.openDatabase('example.db', config))
.then(db => db.exec('SELECT 1'))
.then(results => console.log('results: ', results))
This gives you persistent, local-first SQL with active-active replication in your web browser 🤯. Use the db client as normal (see the API documentation here), with the proviso that the methods are now async (they return promises rather than direct values).
Note that the path to the worker.js must be relative and the worker.js file must survive any build / bundling process. This is handled automatically by most bundlers, including Esbuild (as of #2508), Rollup and Webpack.
See the Web example application for more information.
wa-sqlite
ElectricSQL also supports running in the browser using wa-sqlite. Instantiation is simpler, without requiring the web worker machinery:
import { ElectricNamespace } from 'electric-sql'
import { start, ElectricDatabase } from 'electric-sql/wa-sqlite'
import config from '.electric/@config'
const { db, electric } = await start('electric.db', '', config)
db.electric = electric // for hooks that expect `electric` to be present on the `db`
See the web-wa-sqlite example for more information.
Edge
We support edge applications using Node.js. We aim to support other languages soon (starting with Elixir). In the meantime, you can use ElectricSQL from any server-side application via Postgres.
Node
We support Node.js via the better-sqlite3 driver. Note that this is not the default node-sqlite3 adapter. See this rationale for context.
Make sure you’ve bundled your migrations, instantiate your driver as normal and then electrify it:
import Database from 'better-sqlite3'
import { electrify } from 'electric-sql/node'
import config from '.electric/@config'
const original = new Database('example.db')
electrify(original, config).then((db) => {
// Use as normal, e.g.:
const stmt = db.prepare('select foo from bar')
const results = stmt.all()
console.log('query results: ', results)
})
Postgres
As well as extending SQLite in the local device, ElectricSQL also provisions a geo-distributed cloud Postgres database. Backend applications can use this to integrate with an ElectricSQL system. Either for primary OLTP workloads, or for management and reporting purposes.
Postgres credentials are provided when you create an application using the ElectricSQL console. You can then connect as standard using any Postgres driver. It’s entirely a standard, fully-featured Postgres system. The differences to be aware of are:
- your credentials are scoped to a user without DDL access permissions
- migrations must be applied using
electric migrations sync
Generic
You can integrate any SQLite database driver by adapting it to the ElectricSQL DatabaseAdapter
interface.
Probably the best guidance for this is to look at the existing driver implementations and to note the interface signatures in drivers/generic/database.ts.
First ensure that your driver client has an isGenericDatabase
property set to true
:
MyDriverClass.prototype.isGenericDatabase = true
Then provide an ElectricDatabase
class that implements ProxyWrapper
:
import { Database } from 'electric-sql/generic'
import { ElectricNamespace } from 'electric-sql/electric'
import { ProxyWrapper } from 'electric-sql/proxy'
export class ElectricDatabase implements ProxyWrapper {
_db: Database
electric: ElectricNamespace
constructor(db: Database, namespace: ElectricNamespace) {
this._db = db
this.electric = namespace
}
_setOriginal(db: Database): void {
this._db = db
}
_getOriginal(): Database {
return this._db
}
}
Then implement DatabaseAdapter
:
import { Database } from 'electric-sql/generic'
import {
BindParams,
QualifiedTablename,
Row,
parseTableNames
} from 'electric-sql/util'
export class DatabaseAdapter {
db: Database
constructor(db: Database) {
this.db = db
}
async run(statements): Promise<void> {
// XXX implement using your driver API.
await this.db.runStatementsInTransaction(statements)
}
async query(sql: string, bindParams: BindParams = []): Promise<Row[]> {
// XXX implement using your driver API.
return await this.db.runQuery(sql, bindParams)
}
tableNames(sql: string): QualifiedTablename[] {
return parseTableNames(sql)
}
}
Wrap up into an bespoke electrify function:
import {
ElectricNamespace,
ElectrifyOptions,
electrify as baseElectrify
} from 'electric-sql/electric'
import {
BundleMigrator,
Database,
ElectrifiedDatabase,
EventNotifier,
globalRegistry
} from 'electric-sql/generic'
// Your implementations
import { DatabaseAdapter } from './adapter'
import { ElectricDatabase } from './database'
export const electrify = async (db: Database, dbName: string, opts: ElectrifyOptions = {}): Promise<ElectrifiedDatabase> => {
const adapter = opts.adapter || new DatabaseAdapter(db)
const migrator = opts.migrator || new BundleMigrator(adapter, opts.migrations)
const notifier = opts.notifier || new EventNotifier(dbName)
const registry = opts.registry || globalRegistry
const namespace = new ElectricNamespace(adapter, notifier)
const electric = new ElectricDatabase(db, namespace)
const electrified = await baseElectrify(dbName, db, electric, adapter, migrator, notifier, registry)
return electrified as unknown as ElectrifiedDatabase
}
You can now electrify your database driver:
import { electrify } from './index'
import config from '.electric/@config'
const original = new MyDriverClass('example.db')
electrify(original, 'example.db', config)
.then(db => {
// ... use electrified db client
})