--- name: powersync-js-node description: PowerSync Node.js and Electron integration — CLI setup, background sync, ETL pipelines, and Electron renderer/main process split metadata: tags: nodejs, electron, cli, javascript, typescript, better-sqlite3, offline-first --- # PowerSync Node.js & Electron > **Load this when** building a Node.js CLI tool, background sync process, ETL pipeline, or Electron desktop app. Always load `powersync-js.md` first. Node.js-specific integration for the PowerSync JavaScript SDK. Use this reference alongside `references/sdks/powersync-js.md` when building Node.js CLI tools, background sync processes, ETL pipelines, or Electron desktop apps. | Resource | Description | |----------|-------------| | [Node.js SDK Reference](https://docs.powersync.com/client-sdks/reference/node.md) | Full SDK documentation for Node.js, consult for details beyond the inline examples. | | [Node SDK API Reference](https://powersync-ja.github.io/powersync-js/node-sdk) | Full API reference for `@powersync/node`, consult only when the inline examples don't cover your case. | ## Node.js ### 1. Install ```bash npm install @powersync/node@latest npm install better-sqlite3 # required peer dependency ``` TypeScript types for `better-sqlite3`: ```bash npm install --save-dev @types/better-sqlite3 ``` ### Key Differences from the Web SDK | Aspect | `@powersync/web` | `@powersync/node` | |--------|-----------------|-------------------| | SQLite runtime | WebAssembly (wa-sqlite) | Native (`better-sqlite3`) | | Web Workers | Yes — DB runs in a worker by default | No — runs synchronously in the main thread | | Multi-tab sync | Yes — shared sync worker across browser tabs | No | | UI framework hooks | `@powersync/react`, `@powersync/vue` | None — use imperative API | | Environment | Browser | Node.js 18+ | ### 2. Setup ```ts import { PowerSyncDatabase } from '@powersync/node'; import { AppSchema } from './AppSchema'; import { PowerSyncConnector } from './PowerSyncConnector'; const db = new PowerSyncDatabase({ schema: AppSchema, database: { dbFilename: 'app.db' } }); const connector = new PowerSyncConnector(); await db.connect(connector); ``` `connect()` behaves the same as the web SDK — it starts the sync stream in the background. Use `db.waitForFirstSync()` to wait for data before proceeding. ### Querying No UI hooks are available in Node.js. Use the imperative API from `references/sdks/powersync-js.md` directly: ```ts // One-time queries const lists = await db.getAll('SELECT * FROM lists WHERE owner_id = ?', [userId]); const list = await db.getOptional('SELECT * FROM lists WHERE id = ?', [id]); // Watch for changes (async generator) for await (const result of db.watchWithAsyncGenerator('SELECT * FROM lists')) { console.log('Lists updated:', result.rows._array); } // Writes await db.execute('INSERT INTO lists (id, name) VALUES (uuid(), ?)', ['My List']); await db.writeTransaction(async (tx) => { await tx.execute('DELETE FROM lists WHERE id = ?', [listId]); await tx.execute('DELETE FROM todos WHERE list_id = ?', [listId]); }); ``` ### Use Cases - CLI tools: Sync data from a PowerSync service for offline-capable scripts - Background sync jobs: Keep a local SQLite database up to date with server data for reporting or ETL - Server-side scripts: Read/write to a local PowerSync-managed SQLite database without a browser ### Sync Status The same `db.currentStatus` and `db.registerListener` API applies: ```ts db.registerListener({ statusChanged: (status) => { console.log('Connected:', status.connected, '| Last synced:', status.lastSyncedAt); } }); ``` --- ## Electron Electron apps have two distinct environments — the renderer process (a Chromium browser window) and the main process (Node.js). PowerSync has a different SDK for each. ### Architecture ``` Electron App ├── Renderer Process (Chromium) │ └── Use @powersync/web + @powersync/react or @powersync/vue │ → See references/sdks/powersync-js-react.md or references/sdks/powersync-js-vue.md │ └── Main Process (Node.js) └── Use @powersync/node + better-sqlite3 → Use imperative API — no UI hooks available ``` ### Renderer Process The renderer process is a full browser environment. Set it up exactly like any other web app: ```bash npm install @powersync/web@latest @journeyapps/wa-sqlite@latest @powersync/react@latest ``` Use `PowerSyncContext.Provider` and `useQuery`/`useStatus` hooks as documented in `references/sdks/powersync-js-react.md`. The Web-Specific Options section in `references/sdks/powersync-js.md` (VFS options, multi-tab, `debugMode`) also applies to the renderer process. ### Main Process The main process runs Node.js with no DOM. Use `@powersync/node`: ```bash npm install @powersync/node@latest better-sqlite3 ``` ```ts // main/db.ts import { PowerSyncDatabase } from '@powersync/node'; import { AppSchema } from './AppSchema'; import { PowerSyncConnector } from './PowerSyncConnector'; export const db = new PowerSyncDatabase({ schema: AppSchema, database: { dbFilename: 'app.db' } }); export async function initDb() { const connector = new PowerSyncConnector(); await db.connect(connector); await db.waitForFirstSync(); } ``` ### IPC Pattern If you sync data in the main process and need to expose it to the renderer, use Electron's IPC: ```ts // main/ipcHandlers.ts import { ipcMain } from 'electron'; import { db } from './db'; ipcMain.handle('powersync:query', async (_event, sql: string, params: any[]) => { return db.getAll(sql, params); }); ``` ```ts // renderer — via preload bridge const lists = await window.electron.invoke('powersync:query', 'SELECT * FROM lists', []); ``` However, the more common pattern is to use `@powersync/web` in the renderer and maintain a separate sync connection there — this keeps the data layer entirely in the renderer process and avoids IPC overhead. ## Common Pitfalls ### Using `@powersync/web` in Node.js `@powersync/web` requires browser APIs (`SharedWorker`, `indexedDB`, WebAssembly with browser globals) that are not available in Node.js. Always use `@powersync/node` for the main process or any non-browser Node.js environment. ### Missing `better-sqlite3` native build `better-sqlite3` is a native module that must be compiled for the target platform. In Electron, it must be rebuilt for Electron's Node.js version using `electron-rebuild` or `@electron/rebuild`: ```bash npx @electron/rebuild -f -w better-sqlite3 ``` Run this after any Electron version upgrade.