PostgreJS is a PostgreSQL driver for Node.js built from the wire protocol up - no libpq, no native bindings,
just TypeScript talking directly to PostgreSQL. That from-scratch design is also what makes it fast and light: every
byte on the wire is handled by code written for exactly that purpose, with a binary-first protocol, shared buffers,
and row/column decoding pipelines built to avoid unnecessary allocation, instead of generic string plumbing bolted
onto a client meant for text.
The numbers back it up. In PostgreJS's own benchmark suite - run head-to-head against
pg (node-postgres) and postgres
(postgres.js) on identical workloads - PostgreJS opens a connection up to 3x faster than postgres.js and
2x faster than pg, pushes pooled queries through up to 6.6x faster than postgres.js, and fetches large
result sets nearly 5x faster than pg. It's also the only one of the three with a complete binary wire protocol
across every data type, rather than falling back to text for most of them. See
doc/BENCHMARKS.md for the full methodology and every scenario.
The same benchmarks show it using a fraction of the memory: peak heap usage typically runs 3-7x lower than both pg and postgres.js - as much as 7x lower when streaming cursors or fetching large arrays - and it spends a fraction of the time either of them does in garbage collection. Shared buffers and decode paths that read values straight out of the wire buffer leave far less garbage behind per row, so there's less for the GC to clean up in the first place.
Speed and memory aside, PostgreJS is also the most complete driver of the three: a dynamic sql tag for
composable, parameterized SQL, per-query type mapping, and TC39 Explicit Resource Management (using) support are
unique to it, alongside a feature set most drivers spread across several add-on packages - connection pooling,
prepared statements, server-side cursors, LISTEN/NOTIFY, bulk COPY streams, logical replication, large objects,
two-phase commit, multi-host failover, and SCRAM channel binding - all in the one package, written in
strictly-typed TypeScript from the ground up.
$ npm install postgrejs --savePlease read πΈ DOCUMENTATION πΈ for detailed usage.
- Language: Pure JavaScript, with no native/binary dependencies to compile or ship.
- Strictly typed: Written entirely in TypeScript, with types shipped alongside the package.
- Modern module format: Ships as ESM; Node 20.19+/22.12+ can
require()it from CommonJS code as well. - Promise-based API: Every asynchronous operation returns a promise - no callbacks to wrangle.
- Rigorously tested: A test suite covering the wire protocol, every data type, and connection-handling edge case, run on every push against PostgreSQL 12 through 18.
- Connection Management: Supports both single connection and advanced pooling, providing scalability and efficient resource management.
- Binary Wire Protocol: Implements the full binary wire protocol for all PostgreSQL data types, ensuring robust and efficient data handling.
- Prepared Statements: Named prepared statements for optimized query execution.
- Cursors: Features fast double-link cache cursors for efficient data retrieval.
- Notifications: High-level implementation for PostgreSQL notifications (LISTEN/NOTIFY), enabling real-time data updates.
- Extensibility: Extensible data-types and type mapping to accommodate custom requirements.
- Parameter Binding: Bind parameters with OID mappings for precise and efficient query execution.
- Array Handling: Supports multidimensional arrays with fast binary encoding/decoding.
- Performance Optimization: Low memory utilization and boosted performance through the use of shared buffers.
- Authorization: Supports various password algorithms including Clear text, MD5, and SASL, ensuring secure authentication.
- Bulk Import/Export:
COPY TO STDOUTandCOPY FROM STDINas Node streams, with backpressure in both directions. - Query Pipelining: Pooled queries can share connections so a burst is not capped by pool size - opt-in per call.
- Dynamic SQL: A
sqltag builds statements from composable fragments - values become parameters, names are quoted, andsql.values()/sql.set()write INSERT and UPDATE clauses from objects. - Multiple Hosts: A connection can list several servers and pick one by role
(
target_session_attrs), so a cluster that has failed over is found on the next connect. - Large Objects: File-like access to binary data stored outside the row - seek, partial reads, streams - for
values past what a
byteacolumn can hold. - Logical Replication:
LogicalReplicationstreams committed row changes as an async iterable, decodingpgoutputitself, with client-side filtering and positions confirmed as you consume. - Channel Binding: SCRAM authentication binds itself to the TLS channel when the server offers it, the way libpq does by default, so a relayed login is detected even where the certificate is not verified.
- Two-phase commit:
prepareTransaction()leaves a transaction waiting under a name forcommitPrepared()/rollbackPrepared(), from any connection. - Cancellation: Any call takes an
AbortSignal, which also gives per-query timeouts viaAbortSignal.timeout(). - Flexible Data Retrieval: Can return both array and object rows to suit different data processing needs.
- Resource Management: Auto disposal of resources with the "using" syntax (TC39 Explicit Resource Management), ensuring efficient resource cleanup.
How PostgreJS compares to pg (node-postgres) and
postgres (postgres.js). Every row was checked against the libraries' own
source rather than their documentation β versions compared: PostgreJS 2.23.1, pg 8.23.0, postgres.js 3.4.9. β
built
in Β· π‘ partial or needs a separate package Β· β not supported.
| Feature | PostgreJS | pg | postgres.js |
|---|---|---|---|
| Packaging | |||
| Packages to install | 1 | 4 1 | 1 |
| Module system | ESM | ESM/CJS | ESM/CJS |
| Language | TS | JS 2 | JS 3 |
| Wire protocol | |||
| Simple Query protocol | β | β | β |
| Extended Query protocol | β | β | β |
| Text wire format | β | β | β |
| Binary wire format | β | π‘ 4 | β 5 |
| Per-column format selection | β | β | β |
| High-level API | |||
| Object and array row modes | β | β | β |
| Dynamic SQL helpers | β
sql tag |
β | β |
| Per-query type mapping | β | β 6 | β 6 |
| Query cancellation | β AbortSignal | β | β |
| Per-query timeout | β AbortSignal | β | β 7 |
| Reference counters | Connection / Statement | β | β |
| Caller kept in async error stacks | β | π‘ 8 | π‘ 9 |
| Error located in the SQL text | β line and mark | π‘ offset | π‘ offset |
| TC39 Explicit Resource Management | β | β | β |
| Querying | |||
| Query parameters | β | β | β |
| Parameter type casting | β | π‘ 10 | β |
| Prepared statements | β explicit | β | β automatic |
| Multi-statement scripts | β | β | β |
| Server-side cursors | β | π‘ 11 | β |
COPY TO / COPY FROM |
β | π‘ 12 | β |
| Row count after a COPY | β | β | β |
| Transaction management | |||
| Transaction API | β | β | β |
| Savepoints | β | β | β |
| Two-phase commit API | β | β | π‘ 13 |
| Session management | |||
| Built-in connection pool | β | β | β implicit |
| Pipelining on one connection | β opt-in/call | β opt-in/client | β automatic |
| Graceful shutdown | β | β 14 | β |
| Multiple hosts | β | β | β |
| LISTEN/NOTIFY | β | π‘ 15 | β |
| Data types | |||
| Text encoders | 56 | generic 16 | 14 |
| Text decoders | 56 | 44 | 12 17 |
| Binary encoders | 56 | β | β |
| Binary decoders | 56 | 16 | β |
| Multidimensional arrays | β binary | π‘ text 18 | π‘ text |
| Security | |||
| SSL/TLS | β | β | β |
| Direct TLS negotiation (PG17) | β | β | β |
| Cleartext, MD5, SCRAM-SHA-256 | β | β | β |
SCRAM channel binding (-PLUS) |
β default | β opt-in | β |
| Beyond querying | |||
| Logical replication | β | π‘ 19 | β |
| Large object API | β | β | β |
| Native libpq bindings | β | π‘ 20 | β |
- 1 What it takes to reach the feature set above. PostgreJS and postgres.js ship everything in the one
package you import;
pgneedspg-cursorfor cursors,pg-query-streamfor row streams andpg-copy-streamsfor COPY, each installed and versioned separately. - 2 Types come from the separate
@types/pg; only thepg-protocolandpg-connection-stringsub-packages are written in TypeScript. - 3 Ships a hand-maintained
.d.ts. - 4 Results only - parameters are always stringified. Opt-in per query or per client, and all columns at
once. No binary parser is registered for
bytea, and binary arrays decode onlyint4,int8andtextelements. - 5 Both format-code counts are hardcoded to zero and parameters are stringified, so everything on the wire is text.
- 6 Global or per-client (pg) and per-instance (postgres.js), but not per query.
- 7 Connection-level timeouts only.
- 8 Restored by calling
Error.captureStackTracefrom the promise's own rejection handler, where the synchronous stack is already gone - the caller's frames come from Node's async stack traces, so they survive an awaited chain but not a.then()/.catch()one, and the callback API gets none at all. - 9 Captured at the tagged template, four frames deep;
sql.unsafe()gets none. - 10 A
typesarray on the query config does reach the Parse message, but the same field doubles as the result parser override, so any row-returning query throws inside pg's own handler. Verified usable only for statements that return no rows (pg 8.23.0). - 11 Core has the row-limit primitive; the cursor and stream APIs are separate packages.
- 12 The core
Queryrefuses COPY IN;pg-copy-streamsis required. - 13
sql.prepare(name)runsPREPARE TRANSACTIONinsidebegin, but there is no helper for the other half -COMMIT PREPARED/ROLLBACK PREPAREDhave to be written as raw SQL. - 14
end()destroys the socket when a query is still in flight, so the query is aborted rather than awaited; only a client in pipeline mode waits for drain first. - 15 On the client only - the pool does not forward notifications.
- 16 pg has no per-OID text encoders: a parameter is converted by its JavaScript type rather than by the type it is going into, so there is no count to give.
- 17 Plus every array type, whose OIDs are read from the catalog when a connection opens rather than registered ahead of time.
- 18 Its binary array decoder covers only
int4,int8andtextelements, so everything else falls back to text anyway. - 19 A connection flag exists, but nothing decodes the stream.
- 20
pg-nativeswaps the pure JavaScript protocol for libpq, and its own documentation lists what stops working with it:pg-cursor,pg-query-streamandpg-copy-streamsall "operate directly on the binary stream and therefore are incompatible" - so server-side cursors, row streaming and COPY are what it costs.
PostgreJS implements the full PostgreSQL wire protocol from scratch, with no dependency on pg/libpq.
doc/BENCHMARKS.md compares it against pg (node-postgres) and postgres (postgres.js)
across connection setup, simple/prepared queries, mixed-type decoding, bulk fetches, cursor streaming and pool
concurrency, each library run through its own idiomatic fast path. The numbers there are reproducible on your own
machine via npm run bench against the repo's own
docker/docker-compose.yml Postgres instance; see benchmark/README.md for details.
You can report bugs and discuss features on the GitHub issues page When you open an issue please provide version of NodeJS and PostgreSQL server.
- node >= 20.x
PostgreJS is available under the BSD 3-Clause license.