PostgreSQL with Node.js
By: Arun Kant Pant
Tags: Postgres, Nodejs, Web Development, JavaScript, Database

Starting work on our node app

Adding node-postgres

Back to your project directory, let us start by creating a connection to our database first. For this, we will use the node-postgres module.

npm i pg

Next, create a new directory called db and add an index.js file to it.

This file will hold our connection pool config which we can import and use throughout our project.

Open the db/ index.js file, and add the following lines:

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

const pool = new Pool();

We will be using a connection pool to interact with our database in this blog.

We are using connection pooling instead of single client connections for the benefits it offers. But how do we connect this pool to our database?

Adding the configuration for our database connection

To establish our database connection, we need the following information:

The database user (your system username by default)

(your system username by default) The database host (where is the database running)

(where is the database running) The database port (5432 by default)

(5432 by default) The database name (your postgres username by default)

(your postgres username by default) The database password

This information should be easy to find for your local configuration.

Real world projects will often use environment variables to pick up this info. We will therefore create a .env file, with all of this information in the main directory of your project.

Add the following entries to the file, replacing the keys with your own local database configuration for PGUSER, PGPASSWORD and PGDATABASE. The other entries should be the same in most cases.

PGUSER='arunpant'

PGHOST='localhost'

PGDATABASE='arunpant'

PGPASSWORD='mySuperSecretPassword'

PGPORT=5432

PORT=8083

If you remember our app.js file:

const PORT = process.env.PORT || 8080;

To make sure environment variables like PORT are picked up, we will use the dotenv npm package.

npm i dotenv

And in app.js:

const express = require('express');

const app = express(); require('dotenv').config(); ...

Creating the query generator

Let us go back to our db/index.js file, to work on the rest of the contents. We will make a query function which we can export to other places in our app for querying.

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

const pool = new Pool(); module.exports = {

async query(text, params) {

const res = await pool.query(text, params);

return res;

}

}; // text will be something like 'SELECT * FROM $1'

// params something like this array: ['users'] i.e. the table name

// $1 => replaced by users in final query

The text will be our query, and the params will be the dynamic elements of this query, which are added separately to prevent SQL injection attacks.

Since the pool.query operation could fail due to a number of reasons, such as constraint violations, let us add in some basic error handling and logging as well:

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

const pool = new Pool(); module.exports = {

async query(text, params) { // invocation timestamp for the query method

const start = Date.now(); try {

const res = await pool.query(text, params); // time elapsed since invocation to execution

const duration = Date.now() - start; console.log(

'executed query',

{text, duration, rows: res.rowCount}

);

return res;

} catch (error) {

console.log('error in query', {text});

throw error;

}

}

};

We have encapsulated our code in a try catch block, and added some basic logging.

Node-postgres will add error metadata to the thrown error. We will soon use one such error code to identify a UNIQUE constraint violation for usernames.

We can now export this query method, and pass in a query text and its parameters to execute the query.

Creating the flow for signing up a user

We will create a models folder within our project, and add a User.js file to it.

Let us first add the basic constructor for our User in this file:

function User ({

username,

email,

password,

bio=`Hi, I am ${username}`

}) {

this.username = username;

this.email = email;

this.password = password;

this.bio = bio;

};

We will now add a method to its prototype, for the actual user creation:

const db = require('../db'); //User constructor

function User ({

username,

email,

password,

bio=`Hi, I am ${username}`

}) {

this.username = username;

this.email = email;

this.password = password;

this.bio = bio;

}; // add a createUser method to the prototype

User.prototype.createUser = async function() {

try {

const { rows } = await db.query( `INSERT INTO users(username, email, password, bio)

VALUES ($1, $2, $3, $4)`,

[this.username, this.email, this.password, this.bio] ); return rows;

} catch (error) {

throw error;

}

}; module.exports = User; // db.query: the query method we exported earlier from db/index.js

By default node-postgres reads rows and collects them into JavaScript objects with the keys matching the column names and the values matching the corresponding row value for each column.

The { rows } object that we de-structured will hold this information, in case there is any. It will hold an array of objects for all the matching rows for a particular query.

With this we have our basic User model in place.

Creating the controller for user signup

Let us create another folder called controllers in our project, and add a users.js file to it.

Let us add the controller code to this file.

const User = require('../models/User'); exports.postSignup = async (req, res, next) => {

//getting user data from request body

const {username, password, email, bio} = req.body;

try {

const user = new User({username, password, email, bio});

const result = await user.createUser();

res.send(user);

} catch (error) {

next(error);

}

}; Remember the createUser method we added to the User model prototype? It is therefore available on our user model instance above.

user.createUser() will execute our user insertion query. This query could fail, in case a UNIQUE constraint is violated for example. We have a UNIQUE constraint on the username, and if someone tried to create a user with the same username, the createUser method would throw an error.

This error would hold metadata for that particular kind of constraint violation, including a code which we can use to identify that it was a UNIQUE constraint violation indeed.

Postgres comes with specific error codes, a list of which you can view here.

Here is a snippet of some integrity constraint violation error codes:

Class 23 — Integrity Constraint Violation

23000 integrity_constraint_violation

23001 restrict_violation

23502 not_null_violation

23503 foreign_key_violation

23505 unique_violation

23514 check_violation

23P01 exclusion_violation

We won’t be covering all of the error scenarios given the scope of this blog, so let’s just add a case for when the UNIQUE constraint is violated.

Back in your controller, modify the code as follows:

... exports.postSignup = async (req, res, next) => {

const {username, password, email, bio} = req.body;

try {

const user = new User({username, password, email, bio});

const result = await user.createUser();

res.send(user);

} catch (error) { const errorToThrow = new Error();

switch (error?.code) {

case '23505':

errorToThrow.message = 'User already exists';

errorToThrow.statusCode = 403;

break;

default:

errorToThrow.statusCode = 500;

} //pass error to next()

next(errorToThrow); }

};

When a query operation runs into an error, the particular postgres error code (23505 in this case) is added to the error object.

Remember, we were checking for violation of this particular code:

23505 unique_violation

Our catch block will simply next() this error, and we can add the familiar express error handler to catch such errors with a message and statusCode property in the main app.js file:

... app.use((err, req, res, next) => {

if(err.statusCode) {

res.status(err.statusCode).send(err.message);

} else {

console.log(err);

res.status(500).send('Something unexpected happened');

}

}); const PORT = process.env.PORT || 8080; ...

We will now create a route which uses this controller, which is pretty straightforward.

Create a folder called routes in your project, and add a users.js file to it:

const express = require('express');

const router = express.Router(); const usersController = require('../controllers/users'); router.post('/signup', usersController.postSignup); module.exports = router;

And we can register this route in our app.js file:

... const usersRoute = require('./routes/users'); app.use(usersRoute); ...

With this, we can now check whether our API works as intended using Postman.

Verifying API behaviour

Let us first start the project:

node app.js

Let us make a POST request to the ‘/signup’ route using postman, for a brand new user: