local-postgres or PGlite?
Choose
local-postgreswhen compatibility with a normal PostgreSQL server is the requirement; choose PGlite when PostgreSQL needs to run inside a JavaScript process or browser.
Both projects make PostgreSQL available without Docker, but they provide
different execution models. local-postgres starts native PostgreSQL binaries
as a server. PGlite embeds a WebAssembly build of
PostgreSQL behind a TypeScript API.
Compare the Execution Models
| Decision | local-postgres |
PGlite |
|---|---|---|
| PostgreSQL runtime | Native PostgreSQL executable | PostgreSQL compiled to WebAssembly |
| Supported hosts | Node.js on macOS, Linux, or Windows | Browsers, Node.js, Bun, and Deno |
| Primary interface | Standard TCP or Unix-socket PostgreSQL connections | In-process TypeScript query API |
| Existing clients and CLIs | Connect directly with pg, psql, migration tools, ORMs, and other PostgreSQL clients |
Use the PGlite API or an additional adapter; PGlite Socket can expose a PostgreSQL-compatible server in Node.js |
| Concurrent clients | Native PostgreSQL connection and process model | A PGlite instance is single-user; PGlite Socket can multiplex client connections |
| Persistence | A normal PostgreSQL data directory | Memory, a host filesystem, or browser storage, depending on the PGlite filesystem |
| PostgreSQL versions | Use an installed version or select a managed native binary | Use the PostgreSQL version built into the installed PGlite release |
| Extensions | Use extensions available to the selected native PostgreSQL installation | Use extensions compiled and packaged for PGlite; see the PGlite extension catalog |
| Reactive and local-first APIs | Not included | PGlite provides live-query and synchronization-oriented integrations |
Neither execution model is universally more accurate or more convenient. The important question is whether the surrounding application expects a server or an embedded database.
Choose local-postgres for Server Compatibility
Use local-postgres when the code under test or the local tool should connect
to PostgreSQL exactly as it connects to another server. This includes workflows
that:
- exercise a PostgreSQL driver over TCP or a Unix socket
- invoke
psql, a migration CLI, or another child process - open multiple independent client connections
- depend on behavior or extensions from a native PostgreSQL installation
- need to select a specific native PostgreSQL version
For example, the returned connection string can be passed to both application code and an external tool:
import { spawn } from 'node:child_process'
import { startPostgres } from 'local-postgres'
const postgres = await startPostgres({
dataDir: '.postgres/test',
database: 'app_test',
})
try {
await runTests(postgres.connectionString)
const migrate = spawn('pnpm', ['run', 'migrate'], {
env: { ...process.env, ...postgres.env },
stdio: 'inherit',
})
await new Promise<void>((resolve, reject) => {
migrate.once('exit', (code) =>
code === 0 ? resolve() : reject(new Error(`migrate exited ${code}`)),
)
migrate.once('error', reject)
})
} finally {
await postgres.stop()
}
This starts a separate PostgreSQL process. The machine therefore needs either an installed PostgreSQL or permission to download a managed binary. See Postgres Binaries for those choices.
Choose PGlite for an Embedded Database
Use PGlite when the database should live inside the JavaScript application. This is the stronger fit when a project:
- runs PostgreSQL in a browser
- wants an in-memory database through an in-process API
- cannot rely on native PostgreSQL executables
- uses PGlite's live-query, worker, or local-first integrations
- only needs extensions available as PGlite-compatible builds
A minimal PGlite instance accepts queries without opening a network listener:
import { PGlite } from '@electric-sql/pglite'
const postgres = await PGlite.create('memory://')
try {
const result = await postgres.query<{ value: number }>('select 42 as value')
console.log(result.rows[0].value)
} finally {
await postgres.close()
}
The embedded API is a useful boundary of its own, but it is not the same test boundary as connecting an unmodified PostgreSQL client to a native server.
Decide from the Boundary You Need to Test
Choose based on the production boundary that matters to the test or tool:
- If correctness depends on client connections, authentication, server
processes, native extensions, or external PostgreSQL tools, use
local-postgres. - If correctness depends on browser storage, an embedded query API, workers, or PGlite-specific reactive behavior, use PGlite.
- If the code is mostly portable SQL, either can work. Run a representative test against the same execution model used by the application before relying on the substitute.
For an isolated native test setup, continue with Test Fixtures. For the embedded alternative, see the PGlite documentation.