Using PostgreSQL with Node.js and node-postgres

Introduction

In this article, we will discuss how to integrate PostgreSQL with Node.js.

In order to better follow this article, we'd recommend that you have prior experience using Node.js and SQL statements. We will be using simple javascript ES6 syntax in this article.

There are a few different clients you can use to integrate PostgreSQL with Node.js. In this article, we will be using the node-postgres module. It is a popular and matured module compared to other PostgreSQL clients.

Also, you can use PostgreSQL with an ORM such as Sequelize as well. But we won't use using such an ORM module in this article. Instead, we will use plain SQL queries, which you can then build off of for more complex database interactions.

PostgreSQL

PostgreSQL is a popular SQL database. It has been in active development for the last 30+ years and is considered to be one of the most advanced relational databases out there. PostgreSQL is also easy to learn and setup compared to other relational databases available. Because of its free and open-source nature, this is a popular choice among startups.

PostgreSQL is a cross-platform database that runs on all major operating systems. The configuration and access/database creation slightly differs between operating systems, though.

In this article, we'll will be using Ubuntu 18.04, which is a popular Linux platform and includes PostgreSQL by default. Some steps might be a little bit different if you are using a different operating system.

Project Configuration

Let's get started with a simple blank Node.js project with the default settings:

$ npm init -y

Then, let's use npm to install the node-postgres module, which will be used to connect to and interact with Postgres:

$ npm install --save pg

Implementing CRUD Operations

With our project bootstrapped, let's go ahead and configure the database. After that, we'll write some basic CRUD functionality.

Database Configuration

As with all relational databases, we'll start off by creating one and connecting to it. You can either use the CLI or a GUI-based client to do this. Since it's very simple to set up through the CLI, we'll be doing just that.

For Ubuntu, the default psql command will enter the CLI. PostgreSQL will create a user called postgres to access the database in Linux-based platforms. Thus, we can use the following command to login as the postgres user:

$ sudo -i -u postgres

Then enter the CLI by running:

$ psql

You should see a command shell similar to this:

postgresql psql command

To view the currently present databases, let's use the \list or \l command:

postgresql list command

And now, let's create our own using an SQL query:

CREATE DATABASE testdb;

By running this command, we're creating a testdb database and greeted with the output, confirming our command:

CREATE DATABASE

Since the database is created, we can now access it. While PostgreSQL creates a default postgres user, the password isn't set by default. If you'd like to set your password (rather than leaving it blank), use the \password command:

changing postgresql password using the cli

With your password set, we're ready to use the database in our project.

Connecting to the Database

You have two options that you can connect to a PostgreSQL server with the node-postgres module. One of the options is to use a single client. The other method is to use a connection pool. However, if your application is using the database very frequently, the pool will be a better option than using a single client.

Connecting to the database using the node-postgres module can be done in two ways - using a single client and using a connection pool.

We will take a look at how to use a connection pool to connect to the database later in this article. For the moment, let's connect to the database using a single client for brevity and simplicity:

const { Client } = require('pg');

const client = new Client({
    user: 'postgres',
    host: 'localhost',
    database: 'testdb',
    password: '1234abcd',
    port: 5432,
});

client.connect();

Here, we've manually configured the options. However, you can connect to the database without passing any of those:

const { Client } = require('pg');

const client = new Client();
client.connect();

But then again, Node needs to know how to connect to the database, so we'll supply them through environment variables:

PGUSER=dbuser
PGHOST=database.server.com
PGPASSWORD=secretpassword
PGDATABASE=mydb
PGPORT=3211

If you haven't configured them yourself, the module will use the default values:

PGHOST='localhost'
PGUSER=process.env.USER
PGDATABASE=process.env.USER
PGPASSWORD=null
PGPORT=5432

On Linux, the process.env.USER will hold the value for the current user that is logged in.

Creating Tables

With the database prepped for data-insertion, let's create some tables to store our data in. As with all SQL-based databases, we'll use the CREATE TABLE query:

CREATE TABLE [table_name] (
    [column1] [datatype],
    [column2] [datatype],
    [column3] [datatype],
   ....
);

A table consists of columns, and each column has a data type. For example, a firstName column would have varchar as the data type, which represents a String of variable size.

If you'd like to read more about the supported data types, the PostgreSQL Documentation lists them nicely.

That being said, we can use this query to create a table in the database:

const query = `
CREATE TABLE users (
    email varchar,
    firstName varchar,
    lastName varchar,
    age int
);
`;

To actually run this query against the database, we use the query() function from the client object we set up before:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Table is successfully created');
    client.end();
});

Note: Don't forget to end() your connection to the client after you've ran the query.

Running this code will create our table and print out:

Table is successfully created

This can also be achieved using promises and async/await. Since a database call might fail, it makes more sense to use promises:

client
    .query(query)
    .then(res => {
        console.log('Table is successfully created');
    })
    .catch(err => {
        console.error(err);
    })
    .finally(() => {
        client.end();
    });

As you can see in the example we can use the finally block to close the connection with the database. So even if the query threw an err, the connection will be closed.

Alternatively, we can use the async/await syntax as well:

try {
    const res = await client.query(query);
    console.log('Table is successfully created');
} catch (err) {
    console.log(err.stack);
} finally {
    client.close();
}

All of these approaches should yield the same result:

Table is successfully created

To verify this, let's use the psql command line interface to inspect the DB. Open a terminal, start the shell with psql, and select the database using the \c [database] command. \c is shorthand for \connect:

\c testdb

Then you can list the tables in the database testdb by running the \dt command:

postgresql list tables

You can also query for specific tables by providing their names:

testdb=# \dt FOO

This query will display the table named FOO.

Creating/Inserting Data

We can use the SQL INSERT INTO statement to insert data into a table:

INSERT INTO [table_name] ([column1], [column2], [column3], ...)
VALUES ([value1], [value2], [value3], ...);

To make this query concrete, let's insert our own values and construct a query:

const query = `
INSERT INTO users (email, firstName, lastName, age)
VALUES ('[email protected]', 'john', 'doe', 21)
`;

And finally, let's run the query against the database:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Data insert successful');
    client.end();
});

Note: The same as last time, this function can be written using the async/await syntax. These additional examples are omitted for brevity.

Running this code will insert a user into our database and print out:

Data insert successful

To verify this, in our testdb database, run the SELECT statement:

SELECT * from users;

We can obviously see that the user was indeed created successfully:

postgresql showing table data

Retrieving/Selecting Data

To retrieve data from the database, the SELECT statement is used:

SELECT [column1], [column2], ...
FROM [table_name]
WHERE [condition];

You can select specific columns by specifying them or select all the fields of a table using the * wildcard. Optionally, you can get creative with more conditionals using the WHERE statement.

Here we select all rows and all columns from the users database:

const query = `
SELECT *
FROM users
`;

Now, to run this query against the database, we'll use the client again:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    for (let row of res.rows) {
        console.log(row);
    }
    client.end();
});

Running this code will yield:

{
email: '[email protected]',
firstname: 'john',
lastname: 'doe',
age: 21
}
{
email: '[email protected]',
firstname: 'anna',
lastname: 'dias',
age: 35
}

This query returns all users added to the database. You can also filter the users by their fields.

For example, if we wanted to return all users younger than 30, we'd add a WHERE clause:

const query = `
SELECT *
FROM users
WHERE age<30
`;

And then, we'd run it against the database:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    for (let row of res.rows) {
        console.log(row);
    }
    client.end();
});

Running this code will yield:

{
email: '[email protected]',
firstname: 'john',
lastname: 'doe',
age: 21
}

Updating Data

To update data that already exists, we can use the UPDATE statement:

UPDATE [table_name]
SET [column1] = [value1], [column2] = [value2], ...
WHERE [condition];

You can set each updated value for each column with the SET keyword. After the WHERE clause, you can define the condition of which entries should be updated.

Let's populate our query:

const query = `
UPDATE users
SET age = 22
WHERE email = '[email protected]'
`;

Now, let's run the query against the database:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    if (err) {
        console.error(err);
        return;
    }
    console.log('Data update successful');
    client.end();
});

Running this piece of code will update the entries satisfying the WHERE clause and print out:

Data update successful

To verify, let's check our database:

postgresql showing table data

Deleting Data

Finally, to delete data, we can use the DELETE statement:

DELETE FROM [table_name]
WHERE [condition];

Be careful with this statement as you might accidentally delete more than you're aiming for.

Let's populate our query:

const query = `
DELETE FROM users
WHERE email = '[email protected]'
`;

And finally, run it against the database:

client.query(query, (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    if (err) {
        console.error(err);
        return;
    }
    console.log('Data delete successful');
    client.end();
});

Running this code will delete the entry satisfying the WHERE clause and print out:

Data delete successful

To verify, let's take a look at the database:

postgresql showing table data

Pooling

If your application is using the database frequently, using a single client connection to the database will likely slow down the application when you have many user requests. The easiest and most convenient way to address that problem is to use a connection pool.

Usually, when a new client connects to the database, the process of establishing a connection and authenticating takes around 20-30 milliseconds. This is significant when you're running more queries leading to seconds of delay, which will likely end up being an unsatisfactory end-user experience.

Also, the PostgreSQL server can only handle a limited number of clients at a given time which will depend on your server memory. So if 100 queries are going on in a second - this limitation might crash your server.

Additionally, the client can process only one request at a time for a single connection which further slows things down.

In a situation like this, you can use the pg-pool module to solve that.

Creating a Pool

First import the Pool Class from the pg module:

const { Pool } = require('pg');

Then, let's create a new pool object:

const pool = new Pool({
    user: 'postgres',
    host: 'localhost',
    database: 'testdb',
    password: '1234abcd',
    port: 5432,
});

If you do not configure the username, host, and other properties, you'd have to define environment variables for these in a configuration file. It's pretty much the same as when configuring a single client.

Next, let's define an error handler for the pool. If there are any errors throwing from the pool, the callback in this event will be fired:

pool.on('error', (err, client) => {
    console.error('Error:', err);
});

This covers us in case of a network error.

Then, using the pool object, we connect to the database and use a client in that pool to execute a query:

const query = `
SELECT *
FROM users
`;

pool.connect((err, client, done) => {
    if (err) throw err;
    client.query(query, (err, res) => {
        done();
        if (err) {
            console.log(err.stack);
        } else {
            for (let row of res.rows) {
                console.log(row);
            }
        }
    });
});

This should yield:

{
  email: '[email protected]',
  firstname: 'john',
  lastname: 'doe',
  age: 21
}
{
  email: '[email protected]',
  firstname: 'anna',
  lastname: 'dias',
  age: 35
}

Again, it makes more sense to use promises in this case:

pool.connect()
    .then((client) => {
        client.query(query)
            .then(res => {
                for (let row of res.rows) {
                    console.log(row);
                }
            })
            .catch(err => {
                console.error(err);
            });
    })
    .catch(err => {
        console.error(err);
    });

Or even the async/await syntax:

(async () => {
    try {
        const client = await pool.connect();
        const res = await client.query(query);

        for (let row of res.rows) {
            console.log(row);
        }
    } catch (err) {
        console.error(err);
    }
})();

Using the Cursor to Read Large Queries

Usually, the data received from a query is loaded straight into the memory. The larger the data set, the higher the memory usage will be.

So when you are trying to query a large data set that might contain tends of thousands of records - it's highly inefficient to load it all in the memory and oftentimes, it's plain impossible. A cursor can help you in a situation like this by retrieving a limited number of records at a time.

In a sense, using a cursor is similar to streaming data since you'll access it sequentially in smaller blocks. In order to use the cursor, we have to install the pg-cursor module first:

$ npm install --save pg pg-cursor

We'll be passing a new Cursor to the query() function. The cursor won't actually retrieve any information until we specify the limit using the read() method:

const { Pool } = require('pg');
const Cursor = require('pg-cursor');

const pool = new Pool({
    user: 'postgres',
    host: 'localhost',
    database: 'testdb',
    password: '1234abcd',
    port: 5432,
});

(async () => {
    const client = await pool.connect();
    const query = 'SELECT * FROM users';

    const cursor = await client.query(new Cursor(query));

    cursor.read(1, (err, rows) => {
        console.log('We got the first row set');
        console.log(rows);

        cursor.read(1, (err, rows) => {
            console.log('This is the next row set');
            console.log(rows);
        });
    });
})();

The cursor's read() method lets us define how many rows we want to retrieve from the current cursor instance. In this example for simplicity, we have limited the rows for one record. Then we have read another set of rows after that.

If you've reached the end of the rows in the database, the rows array will be of length 0.

Conclusion

PostgreSQL is a really popular, free, open-source relational database. The node-postgres module is a widely-employed and matured module that bridges Node.js to PostgreSQL.

In this article, we've set up a PostgreSQL database and developed basic CRUD functionality through a simple Node.js script. Then, we've explored pooling support and the usage of cursors to limit the retrieved data.

As always the source code is available on GitHub.

Author image
About Janith Kasun
Colombo, Sri Lanka Twitter