Notifications (LISTEN/NOTIFY)
PostgreSQL's LISTEN/NOTIFY mechanism lets a session subscribe to named
channels and receive asynchronous messages sent with NOTIFY or
pg_notify(). postgrejs wraps this with a channel-based API on both
Connection and Pool, plus a raw 'notification' event for anything that
does not fit the callback model.
Listening on a Connection
import { Connection } from 'postgrejs';
const connection = new Connection();
await connection.connect();
await connection.listen('my_channel', msg => {
console.log(msg.channel, msg.payload, msg.processId);
});
// From this or any other session:
await connection.query(`NOTIFY my_channel, 'hello'`);
listen() sends LISTEN <channel> the first time a callback is registered
for that channel; further callbacks on the same channel are added locally
without another round trip.
| Argument | Type | Default | Description |
|---|---|---|---|
channel | string | - | Channel name — any name PostgreSQL accepts, up to 63 bytes. See below for how case is handled. |
callback | NotificationCallback | - | Invoked with the NotificationMessage on every matching notification. |
Channel name casing
A channel name is a PostgreSQL identifier, and LISTEN foo and LISTEN Foo
are not the same subscription: an unquoted identifier is folded to lower
case by the server, so both LISTEN Foo and NOTIFY Foo really mean
foo. listen()/unListen() fold a name the same way whenever it looks
like it could have been written unquoted, so the key your callback is
registered under and the name the server reports back always match. A
mixed-case channel used to be accepted but never delivered
anything, because the callback was keyed on the original casing while every
incoming notification arrived as the folded one:
await connection.listen('MyChannel', msg => console.log(msg.payload));
await connection.query(`NOTIFY MyChannel, 'hi'`); // folds to 'mychannel' too - delivers
A name that could not have been written unquoted (spaces, punctuation) is not folded; it is passed through, quoted, exactly as given.
To stop receiving notifications:
await connection.unListen('my_channel'); // sends UNLISTEN my_channel
await connection.unListenAll(); // sends UNLISTEN *
unListen() is a no-op if no callback is registered for that channel.
unListenAll() is also called automatically when the connection closes, so a
Connection never leaves a LISTEN registered against the server after it
disconnects.
The NotificationMessage shape
interface NotificationMessage {
processId: number; // backend PID of the NOTIFY-ing session
channel: string;
payload: string;
}
type NotificationCallback = (msg: NotificationMessage) => any;
The raw 'notification' event
Every notification the connection receives - regardless of whether anything
is listen()-ing for it - is also emitted as a 'notification' event on the
Connection:
connection.on('notification', msg => {
console.log(`[${msg.processId}] ${msg.channel}: ${msg.payload}`);
});
This fires even for channels you never called listen() for (for example if
you issued LISTEN yourself with a raw query() call), while listen()
callbacks only fire for their own channel.
Listening on a Pool
Calling pool.listen() does not borrow one of the pool's regular connections:
it opens and keeps one dedicated internal Connection just for LISTEN,
created lazily the first time listen() is called. That connection is
separate from acquire()/release() traffic, so subscriptions survive
however many queries the rest of the pool runs; you do not need to keep a
connection acquired to keep listening.
import { Pool } from 'postgrejs';
const pool = new Pool();
await pool.listen('my_channel', msg => {
console.log(msg.payload);
});
// Any pooled connection can trigger it:
await pool.query(`NOTIFY my_channel, 'hello from the pool'`);
pool.unListen(channel) and pool.unListenAll() mirror the Connection
methods. Once the last channel is unregistered, the pool closes its internal
notification connection; pool.close() also tears it down.
If the notification connection drops unexpectedly (network blip, server
restart), the pool reconnects automatically after a short delay and
re-issues LISTEN for every channel that still has callbacks registered, so
subscriptions resume without any action from the caller.
| Method | Description |
|---|---|
pool.listen(channel, callback) | Registers callback and opens the internal notification connection if needed. |
pool.unListen(channel) | Removes callback(s) for channel; closes the internal connection if no channel remains. |
pool.unListenAll() | Removes every listener and closes the internal connection. |
A full round-trip
import { Connection, Pool } from 'postgrejs';
const listener = new Pool();
const notifier = new Connection();
await notifier.connect();
await listener.listen('orders', msg => {
const order = JSON.parse(msg.payload);
console.log('New order:', order.id);
});
// Elsewhere in the app, or in a trigger via pg_notify():
await notifier.query(
`select pg_notify('orders', $1)`,
{ params: [JSON.stringify({ id: 42 })] },
);
pg_notify(channel, payload) is preferable to the NOTIFY statement when the
channel name or payload is a parameter, since NOTIFY does not accept bind
parameters for the channel name.
See API: Connection and API: Pool for full method signatures.