pg-promise: a PostgreSQL interface for Node.js that goes past promises
PostgreSQL interface for Node.js
At a glance
- What is it?
- pg-promise wraps node-postgres with automatic connections, transactions and a query-formatting engine. This review covers what it does well, where its connection model bites, and how it compares with plain pg.
- Who is it for?
- Adopt pg-promise when a Node.js service needs shared connections, automatic transactions and a formatting engine that escapes values for you, and when the team accepts the task and tx protocol as the only safe way to run multiple queries. Skip it if the workload is a single query per request, or if you want to stay on the bare pg client, since pg-promise wraps it and adds a layer to learn.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 43 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What pg-promise is for, and who should reach for it
The README is blunt about the origin story: at its inception in 2015 the library only added promises to the base driver, hence the name, and the author states that promises are now only a tiny part of it. What remains is a PostgreSQL interface for Node.js built on top of node-postgres, and the additions it advertises are automatic connections, automatic transactions, a query-formatting engine with query generation, a declarative approach to handling query results, global events reporting, and support for external SQL files.
The audience is a Node.js backend where the database work is more than a single statement per request. If a handler needs to run several queries on one connection, or wrap them in a transaction, pg-promise gives that shape a name: task and tx. The library also targets teams that want SQL kept out of JavaScript strings, which is what the Query Files feature and the pg-minify dependency are for.
It is not a query builder and not an ORM. You still write SQL. The value is in connection handling, result-shape enforcement and value escaping, not in generating schema-aware queries.
How the connection and result model actually works
Every query method is based off the generic query method, and the derived methods are named for how many rows you expect: none, one, oneOrNone, many, manyOrNone (aliased as any). The README warns explicitly not to confuse the method name with the number of rows affected, which is irrelevant. The point of picking the right method is that an unexpected row count is rejected as an error rather than silently returning the wrong shape.
The README is equally explicit about query: it acquires and releases the connection, which makes it a poor choice for executing multiple queries at once. That is why task and tx exist. Both expose a connected protocol with additional methods batch, page and sequence, and the protocol can be extended through the extend event. The README calls Chaining Queries a must-read to avoid writing code that misuses connections.
Formatting is decided by the type of the values argument. An array or a single basic type selects index variables like $1 and $2, with indexes from $1 to $100000. An object selects named parameters written as $*propName*, where the delimiters can be {}, (), <>, [] or //. Property values null and undefined both format as null, but a property that does not exist throws an error. The name this refers to the formatting object itself and is inserted as a JSON string.
Two warnings in the README matter in practice. Never use ES6 template strings or manual concatenation to build queries, and never use the ${} form inside a template string, because template strings have no knowledge of how to format values for PostgreSQL. Inside a template string only $(), $<>, $[] or $// are safe.
Installing pg-promise and running a first parameterized query
The package is published on npm as pg-promise, and package.json declares Node.js >=16.0 as the engine requirement. The README does not include a copy-paste install command, so the command below is the standard npm install for the published package name.
npm install pg-promiseCreating a Database object is documented in the Official Documentation linked from the README, not in the README itself. The README's formatting examples assume such an object already exists and is named db.
The first example in the README is an index-variable query against a product table. Note the array as the second argument: the README advises passing index variables inside an array to avoid ambiguity, because single-value parametrization only works for number, bigint, string, boolean, Date and null.
await db.any('SELECT * FROM product WHERE price BETWEEN $1 AND $2', [1, 10])Named parameters take an object instead. This example, taken from the README, mixes three delimiter styles in one statement and uses a nested property name.first.
await db.none('INSERT INTO users(first_name, last_name, age) VALUES(${name.first}, $<name.last>, $/age/)', {
name: {first: 'John', last: 'Dow'},
age: 30
});What you should see is the query executed through the library's formatting engine, with values escaped for PostgreSQL. If you pass an object and the query uses $1 instead, or the reverse, the formatting will not match your intent, which is the most common first mistake with this library.
The connection model is the sharpest limitation
The single most important constraint is stated in the README in a section marked IMPORTANT: the generic query method acquires and releases a connection, so it is unsuitable for running multiple queries at once. A developer who writes three sequential await db.one(...) calls in one request handler is, by the library's own account, misusing connections. The correct path is task or tx, which hold the connection for the duration of the callback.
This is a design trade-off, not a bug. It keeps the simple case simple and pushes the cost of stateful work onto an explicit API. But it means the library's surface is larger than it first appears: you need to understand query, task, tx, txIf, connect, and the connected protocol's batch, page and sequence methods before you can be confident a handler is correct under load.
Transactions carry their own documented limitations. The README has a Limitations subsection under Nested Transactions, and it also documents configurable and conditional transactions. Those constraints are not summarized in the README text available here, so anyone nesting transactions should read that subsection directly rather than assume nesting behaves like a savepoint stack.
One more boundary: stream is listed among the methods, but pg-query-stream is a peer dependency, not a regular one. The package.json lists it under peerDependencies at version 4.17.0. If you call stream without installing it, you own that gap.
pg-promise versus node-postgres
The real alternative is node-postgres, the pg package that pg-promise depends on: package.json pins pg at 8.23.0. This is not a case of two unrelated libraries; pg-promise is a layer above pg, and the README says so in its first line of the About section.
The difference in approach is what each one hands you. With pg you get a client and a pool, and you write your own connection acquisition, release and transaction boilerplate. With pg-promise you get named methods that encode the expected row count, a task and tx protocol that manages the connection for you, a formatting engine that escapes values, query files for external SQL, and global events reporting for central handling.
The cost of that layer is indirection. Errors from the underlying driver arrive through pg-promise's own surface, and the formatting engine introduces its own syntax rules that a pg user never has to learn. The README's own warning about template strings exists precisely because the formatting syntax overlaps with JavaScript's.
If your service runs one query per request and you are comfortable managing the pool yourself, pg is the smaller dependency. If you find yourself writing the same acquire, try, commit, release block repeatedly, pg-promise is the layer that block was meant to become.
Maintenance, licence and the cost of upgrading
The repository is not archived. The most recent push recorded is 2026-08-20, and the release 12.7.1 carries the same date, with 12.7.0 on 2026-06-26 and 12.6.2 on 2026-03-06 before it. That is a steady release rhythm over the past several months, and the project is maintained rather than dormant.
The licence is MIT, declared in package.json and present as a LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution with the licence and copyright notice retained. That is a description of the licence text, not legal advice; if your organisation has rules about dependency licences, the LICENSE file is the thing to read.
The upgrade cost is real but bounded. The dependency list is short: assert-options, pg, pg-minify and spex. Because pg-promise sits on top of pg, a major pg release can ripple through, and the pinned versions in package.json show the project tracks pg closely. The library ships its own TypeScript definitions at typescript/pg-promise.d.ts, so TypeScript users get types without a separate @types package, but those types move with the library's own releases. The README does not document a rollback procedure for a failed upgrade, so pinning a known-good version in your lockfile is the only documented-safe posture available from the repository files.
Editorial conclusion
Adopt pg-promise when a Node.js service needs shared connections, automatic transactions and a formatting engine that escapes values for you, and when the team accepts the task and tx protocol as the only safe way to run multiple queries. Skip it if the workload is a single query per request, or if you want to stay on the bare pg client, since pg-promise wraps it and adds a layer to learn. Before committing, verify the connection-pool settings against your deployment's concurrency, confirm that pg-query-stream is installed if you plan to call stream, and read the nested-transaction limitations in the official documentation, because those constraints decide whether a given transaction shape is expressible at all.
Frequently asked questions
What is pg-promise?
It is a PostgreSQL interface for Node.js built on top of node-postgres. The README says it adds automatic connections, automatic transactions, a query-formatting engine with query generation, declarative result handling, global events reporting and support for external SQL files.
How does pg-promise differ from node-postgres?
pg-promise depends on pg and layers on top of it. Instead of managing a client and pool yourself, you get result-specific methods like one and many, a task and tx protocol that holds a connection across multiple queries, and a formatting engine that escapes values.
How do I install pg-promise?
It is published on npm under the name pg-promise, and package.json requires Node.js 16 or later. The README itself does not include an install command; it points to the Official Documentation for creating the Database object.
Why does pg-promise reject my query when I expect a different number of rows?
The result-specific methods are named for how many rows the query is expected to return, and an unexpected row count is rejected as an error. The README notes that the method name has nothing to do with rows affected by the query.
Can I use ES6 template strings to build pg-promise queries?
The README warns against using template strings or manual concatenation, and specifically against the ${} form inside a template string, because template strings cannot format values for PostgreSQL. Inside a template string only $(), $<>, $[] or $// are safe.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/vitaly-t-pg-promise)