Imagine a scenario where your application instantly reacts to changes in your Postgres database, almost as if it had a sixth sense. That's not just wishful thinking; with PostgreSQL's PG_NOTIFY feature, you can make this a reality. Designed to keep systems in sync, PG_NOTIFY provides a way to trigger notifications on database updates. Let's dive into how you can harness this functionality to elevate data-driven applications and how it can improve event driven actions in you applications.
What Is PG_NOTIFY?
PG_NOTIFY is part of PostgreSQL's LISTEN/NOTIFY mechanism, providing an efficient way to signal applications about database events without continuous polling. When a specified event occurs – typically an update to a table – PG_NOTIFY dispatches a message which Listening processes can catch to perform subsequent actions. This isn't some run-of-the-mill feature; it's a game-changer for maintaining timeliness and reducing unnecessary database load.
How Does It Work?
- Step One: An operation in the database triggers PG_NOTIFY.
- Step Two: A payload consisting of JSON or simple text accompanies the notification.
- Step Three: Applications running
LISTENcommands intercept these payloads. - Step Four: The app acts on the data, eg. sending notifications or syncing services.
Setting Up Notifications
Here's a step-by-step guide on setting up PG_NOTIFY, we will be using node and typescript in this example but can be achieved in other languages and respective postgres clients. Assume we have a user table in our database with the following structure. In this scenario I want to trigger an email when a new user signs up but this could apply to other scenarios eg. update my cache when the user info is updated.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email_address VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Step 1: Create a Trigger Function
Create a postgres function that triggers the PG_NOTIFY event.
CREATE OR REPLACE FUNCTION notify_on_create()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('user_created', NEW.id::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;It is possible to pass the entire updated payload as a json object in the notification payload but it is recommended to pass the id and then fetch the updated row in your listener function because the pg_notify function has a payload limit of 8000 bytes (8kb). If you're certain your payload will never exceed that size you can update the pg_notify call to the following to pass the entire updated payload
PERFORM pg_notify('user_created', NEW.id::text);Step 2: Attach the Trigger to Your Desired Table
Bind the trigger to the action. It could be connect to one or more of the INSERT, UPDATE, or DELETE operations
CREATE TRIGGER user_create_trigger
AFTER INSERT ON user
FOR EACH ROW EXECUTE FUNCTION notify_on_create();Step 3: Listen for Notifications
Setup a listener on your server that connects to the db and processes the update notification. We'll be using the node pg library
import pg from "pg";
const dbClient = new pg.Client("postgresql://postgres@localhost:5432/db");
export const dbEventListener = () => {
console.log("Connecting to database...");
dbClient.connect(async (err) => {
if (err) {
throw new Error("Error connecting to database:", err);
}
console.log("Connected to database successfully");
dbClient.query(`LISTEN user_created`); // The event name used in your pg notify function definition
dbClient.on("notification", async (msg) => {
// The notification payload has the following structure
// interface Notification {
// processId: number;
// channel: string;
// payload?: string | undefined;
//}
console.log(`Notification: ${msg.channel}`);
console.log(`Notification payload: ${msg.payload}`);
});
});
};Step 4: Act On the Data Update
The payload will container the id of the newly created row. We can use that to fetch the user information from the database and handle any related business logic
export const dbEventListener = () => {
console.log("Connecting to database...");
dbClient.connect(async (err) => {
// ... existing code
dbClient.on("notification", async (msg) => {
//...existing code
const user = getUser(msg.payload); // The payload contains the userId
await sendEmail(user.emailAddress);
});
});
};Advantages and Considerations
Using PG_NOTIFY provides the following benefits
- Minimizes Performance Hits: Say goodbye to constant polling, which can weigh down your system like an anchor.
- Heightens Responsiveness: With alerts being real-time, your apps can mirror database changes almost instantaneously.
- Scalability-Friendly: It caters nicely to growing systems, adapting without increasing complexity
Despite the clear benefits, as with all software there are tradeoffs that need to be considered Overuse could lead to notification floods, so careful calibration is crucial. While PG_NOTIFY excels in many environments, extreme scale might require complementary strategies (eg. queues, retries) to ensure performance doesn't take a nosedive or critical operations aren't affected
Conclusion
The PG_NOTIFY function empowers software engineers to react to database updates fluently and immediately. By adopting this approach, you can implement reactive architectures that are both robust and elegant. This real-time responsiveness not only enhances user experience but also optimizes system performance by reducing unnecessary database load. However, it's crucial to calibrate the use of notifications to prevent overload and ensure that the system scales efficiently. With careful implementation, PG_NOTIFY can be a cornerstone in building scalable, efficient, and responsive data-driven applications.
Written by

Tsatsu Adogla-Bessa
Software Engineer
Tsatsu is a software engineer with a focus on building scalable and reliable web and mobile applications. He has a passion for creating high-quality software solutions that are user-friendly and easy to maintain. Tsatsu is also an advocate for clean code and best practices in software development.
View profile →
