# pg-listen: Postgres LISTEN and NOTIFY without the connection pool trap

> pg-listen is a small TypeScript package that gives node-postgres users a reliable way to subscribe to Postgres notifications, with automatic reconnects, health checks and a typed event API. It suits teams using Postgres as a message bus in a Node service, and its peer dependency range still stops at pg 8.x, which is the first thing to check.

**andywer/pg-listen** — 📡 PostgreSQL LISTEN &  NOTIFY for node.js that finally works.

- Repository: https://github.com/andywer/pg-listen
- Stars: 636 · Forks: 35
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/andywer-pg-listen

## The problem with LISTEN on a pooled pg client

Postgres can act as a message broker, sending notifications with arbitrary payloads from one database client to others, and the NOTIFY and LISTEN statements are the mechanism. Doing that from node-postgres directly is not trivial, and the README is specific about why: you cannot use connection pools, and even distinct client connections tend to time out.

That is the actual engineering problem this package solves. A LISTEN subscription is session state bound to one connection, so a pooled client will hand your LISTEN to a different backend connection than the one you subscribed on, and the subscription silently evaporates. A dedicated client avoids that but then has to survive the network dropping it, which is where the timeout problem appears.

The author frames the project as a reaction to that, saying in one sentence that none of the existing packages was working reliably in production, and naming pg-pubsub as an example that does not work reliably, with errors being swallowed, hard to reason about code and no type safety. The listed features are the checklist against those complaints: send and subscribe to messages, continuous connection health checks, automatic reconnects, proper error handling and a type-safe API.

## The subscriber API and its two event surfaces

The API is small, and it splits into two surfaces that serve different purposes. The instance.events emitter carries lifecycle information: connected, error, notification and reconnect. The instance.notifications emitter is the convenient per-channel subscription.

The connected event is emitted once after the initial connection and again after every successful reconnect, and reconnects happen automatically when the package detects that the connection closed or became unresponsive. The reconnect event fires when a connection issue has been detected and a reconnection attempt is started, and it passes the attempt number, which is the hook to use for backoff logic if the default retry timing is not what you want.

The error event is for fatal errors affecting the subscription, and the documentation is direct about the standard response: log the error with console.error and terminate the process with a non-zero exit code. It is usually emitted after multiple reconnect attempts have failed, so an error handler that exits is safe. By contrast the notification event is emitted whenever a notification is received, and the documentation insists that you must have subscribed to the channel with listenTo before you receive anything on it.

The instance.notifications emitter takes a channel name and a payload listener, and it is the shorter path for the common case. The catch is that registering a listener is not the same as subscribing, so you still have to call listenTo on the channel to have the Postgres client actually listening.

## Sending payloads and the connection config it expects

The subscriber is created with createSubscriber, and it accepts the same connection config object that the pg package would take, which means your existing connection string or pool config object can be passed straight in. The example in the documentation passes a connectionString built from a databaseURL pulled from a local config module.

Sending is symmetric with subscribing, through a notify method that takes a channel name and a payload object. The payload in the example is a plain object with a greeting string and a timestamp taken from Date.now, and the receiving listener logs it as passed to notify. That is worth noting: the payload is delivered to the listener as the object you sent, which means the package is handling the serialisation to and from the text that Postgres NOTIFY actually transports.

The limits are Postgres limits, not package limits, and the documentation does not enumerate them. NOTIFY carries a payload in a text field with a hard size limit, and a listener that assumes it receives arbitrary structured data will eventually meet one. If your message is large, this is a queueing problem rather than a notification problem, and no client library changes that.

## Installing and wiring a subscriber into a service

The package is on npm, and the install is a single dependency:

```sh
# using npm:
npm install pg-listen

# using yarn:
yarn add pg-listen
```

pg itself is a peer dependency, not a bundled one, and the accepted range is 7.x or 8.x. That constraint is the most important thing to check before adopting the package, because it means the installed node-postgres major version has to be one the author tested against.

The usage example shows the shape a real service needs. A subscriber is created from a connection string, the notification listener is registered on a channel, the error handler logs and exits, and a process exit handler closes the subscriber:

```js
import createSubscriber from "pg-listen"
import { databaseURL } from "./config"

// Accepts the same connection config object that the "pg" package would take
const subscriber = createSubscriber({ connectionString: databaseURL })

subscriber.notifications.on("my-channel", (payload) => {
  // Payload as passed to subscriber.notify() (see below)
  console.log("Received notification in 'my-channel':", payload)
})
```

The connect function then awaits subscriber.connect and subscriber.listenTo, and a sendSampleMessage function awaits subscriber.notify with the channel and payload. Two operations have to happen in order: connect, then listenTo, before any notification for that channel will arrive.

## Health checks, reconnects and how to debug them

Continuous connection health checks are listed as a feature, and the reconnection behaviour is documented in terms of the package detecting that the connection closed or became unresponsive. What that detection actually is, and how long it waits before deciding a connection is dead, is not described in the documentation available here, so the reconnection latency in a network with a silently dropped connection is not something you can read off the docs.

The practical consequence is that you should treat the connected and reconnect events as your instrumentation. The connected event fires again after every successful reconnect, which gives you a natural place to log that the service re-established its subscription, and the reconnect event with its attempt number gives you a place to alert if retries pile up.

For deeper inspection, the documentation gives one line: set the DEBUG environment variable to pg-listen:* to enable debug logging. The dependency list confirms the mechanism, since debug is a runtime dependency of the package, so that namespace filter is the standard debug wildcard rather than a custom logger. Turning that on in a staging environment before you trust the reconnect path is cheap.

The error event deserves one more note. The documentation calls it a fatal database connection error in the example and the recommended handling is to terminate the process, which is a deliberate design choice rather than an oversight. A Node service supervised by a process manager will be restarted, and it will reconnect to a healthy database on boot.

## The pg peer dependency and the age of the release line

The package manifest says version 1.7.0, and the peer dependency range is pg 7.x or 8.x. The development dependencies pin pg to ^8.0.3, the types to @types/pg ^7.4.13, and the toolchain to TypeScript ^3.3.3333 with tslint rather than eslint. The test runner is ava, with the ava config requiring ts-node/register and setting serial true, and the test:database script spins up a Postgres 10.4 instance in Docker:

```json
    "test:database": "docker run -e POSTGRES_DB=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 --rm postgres:10.4-alpine",
```

The release history is the part to weigh. The tags in the repository are v1.7.0 from 2020-12-19, v1.6.1 from 2020-12-04 and v1.6.0 from 2020-07-24. The repository is not archived and the last push was on 2026-04-02, so the tree has seen work since the last tag, but there has been no published release in nearly six years, and no release notes beyond the tag names to tell you what changed.

The licence is MIT, so the code can be read, copied and vendored, provided the copyright notice is kept, and for a package this small that is a legitimate path. Forking a 1.7.0 release and pointing it at a newer pg is straightforward, since the whole surface is one createSubscriber function and a handful of events.

## When a Postgres channel is the wrong tool

The first case is scale of payload and volume. NOTIFY is designed for small, infrequent signals, and the transport is a text payload inside the database connection rather than a durable queue. There is no persistence, so a notification sent while no client is listening is gone. A listener that reconnects after a five minute outage has no way to recover what it missed, and the connected event only tells you the connection is back, not what was lost.

The second case is a deployment topology that breaks session affinity. The whole design depends on one dedicated connection holding the LISTEN. A connection pooler in transaction mode will reassign the backend, and while LISTEN is session state, some poolers and proxies handle it poorly. If your service connects through a pooler, that is the first thing to verify, and it is a property of your infrastructure rather than of the package.

The third case is a greenfield project. The alternative here is not another npm package, it is a different decision. If you are starting a new Node service and want pub/sub, a dedicated broker such as Redis or a managed queue gives you delivery guarantees, consumer groups, backpressure and a payload size that is not bounded by a database text field, and the operational cost of running one. Postgres LISTEN wins when the database is already there, the messages are small and infrequent, and adding another stateful service to operate is the bigger cost.

## Conclusion

pg-listen is the right choice for a Node service that already uses node-postgres and wants Postgres as a lightweight pub/sub layer without writing reconnect logic by hand. It is the wrong choice for a new project, because the peer dependency range stops at pg 8.x while node-postgres has moved on, and because the notification payload limit and the LISTEN semantics are Postgres constraints the package cannot remove. Before adopting it, check that the pg version in your project satisfies the peer dependency, confirm your connection string passes through a pooler that supports session state such as LISTEN, and read the reconnect and error events rather than assuming a dropped connection is retried silently.

## FAQ

### How do I install pg-listen?

Run npm install pg-listen or yarn add pg-listen. The pg package itself is a peer dependency rather than a bundled one, and the accepted range is 7.x or 8.x.

### How do I subscribe to a Postgres channel with pg-listen?

Register a listener on the instance.notifications emitter, then call listenTo on the channel after connecting. The documentation notes you must subscribe to the channel with listenTo before you receive any notifications for it.

### Does pg-listen reconnect automatically when the database connection drops?

Yes. Reconnects happen automatically when the package detects that the connection closed or became unresponsive, a reconnect event fires with the attempt number, and a connected event is emitted again after each successful reconnect.

### How do I enable debug logging in pg-listen?

Set the DEBUG environment variable to pg-listen:* to enable debug logging, as the documentation states.

### Is pg-listen still maintained?

The repository is not archived and the last push was on 2026-04-02, but the latest published release is v1.7.0 from 2020-12-19. The project is MIT licensed.

## Sources

- [andywer/pg-listen on GitHub](https://github.com/andywer/pg-listen)
- [Issues](https://github.com/andywer/pg-listen/issues)
- [License: MIT](https://github.com/andywer/pg-listen/blob/master/LICENSE)
- [README](https://github.com/andywer/pg-listen/blob/master/README.md)
- [Releases](https://github.com/andywer/pg-listen/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/andywer-pg-listen
