PostgreSQL is a powerful, open-source relational database system. In the Node.js ecosystem, pg-promise is a popular library for interacting with PostgreSQL databases. It provides a powerful set of features for querying and managing data in PostgreSQL, leveraging promises for asynchronous operations.
1.Create a new directory for your project and navigate into it.
mkdir pg-promise-example
cd pg-promise-example
2.Initialize a new Node.js project.
npm init -y
3.Install necessary packages.
npm install pg-promise
Open your PostgreSQL client and create a new database.
CREATE DATABASE pg_promise_example;
We have already installed the pg-promise
package in the previous section. If you haven’t, run:
npm install pg-promise
Create a new file config/database.js
to set up the pg-promise
configuration.
const pgp = require('pg-promise')();
const db = pgp({
host: 'localhost',
port: 5432,
database: 'pg_promise_example',
user: 'your_username',
password: 'your_password'
});
module.exports = db;
Replace 'your_username'
and 'your_password'
with your PostgreSQL username and password.
Configuring pg-promise
Let’s add a new user to the database.
Create a new file models/user.js
:
const db = require('../config/database');
async function createUser(name, email) {
try {
const result = await db.one('INSERT INTO users(name, email) VALUES($1, $2) RETURNING *', [name, email]);
console.log(result);
} catch (error) {
console.error('Error creating user:', error);
}
}
module.exports = { createUser };
Create a new file scripts/createUser.js
to run the createUser
function:
const { createUser } = require('../models/user');
createUser('John Doe', 'john.doe@example.com');
Run the script to add a user:
node scripts/createUser.js
Retrieve all users from the database.
Update models/user.js
:
async function getAllUsers() {
try {
const result = await db.any('SELECT * FROM users');
console.log(result);
} catch (error) {
console.error('Error fetching users:', error);
}
}
module.exports = { createUser, getAllUsers };
Create a new file scripts/getAllUsers.js
to run the getAllUsers
function:
const { getAllUsers } = require('../models/user');
getAllUsers();
Run the script to fetch all users:
node scripts/getAllUsers.js
Update a user’s email.
Update models/user.js
:
async function updateUser(id, email) {
try {
const result = await db.one('UPDATE users SET email = $1 WHERE id = $2 RETURNING *', [email, id]);
console.log(result);
} catch (error) {
console.error('Error updating user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser };
Create a new file scripts/updateUser.js
to run the updateUser
function:
const { updateUser } = require('../models/user');
updateUser(1, 'john.new@example.com');
Run the script to update the user:
node scripts/updateUser.js
Delete a user from the database.
Update models/user.js
:
async function deleteUser(id) {
try {
const result = await db.result('DELETE FROM users WHERE id = $1', [id]);
console.log(result.rowCount);
} catch (error) {
console.error('Error deleting user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser };
Create a new file scripts/deleteUser.js
to run the deleteUser
function:
const { deleteUser } = require('../models/user');
deleteUser(1);
Run the script to delete the user:
node scripts/deleteUser.js
Parameterized queries help prevent SQL injection attacks by separating SQL code from data.
async function findUserByEmail(email) {
try {
const result = await db.oneOrNone('SELECT * FROM users WHERE email = $1', [email]);
console.log(result);
} catch (error) {
console.error('Error finding user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail };
Create a new file scripts/findUserByEmail.js
to run the findUserByEmail
function:
const { findUserByEmail } = require('../models/user');
findUserByEmail('john.doe@example.com');
Run the script to find the user:
node scripts/findUserByEmail.js
pg-promise
provides helper functions to simplify query building.
const { helpers } = require('pg-promise');
async function bulkInsertUsers(users) {
try {
const columns = new helpers.ColumnSet(['name', 'email'], { table: 'users' });
const query = helpers.insert(users, columns) + ' RETURNING *';
const result = await db.any(query);
console.log(result);
} catch (error) {
console.error('Error bulk inserting users:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers };
Create a new file scripts/bulkInsertUsers.js
to run the bulkInsertUsers
function:
const { bulkInsertUsers } = require('../models/user');
bulkInsertUsers([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
]);
Run the script to insert multiple users:
node scripts/bulkInsertUsers.js
Transactions ensure that a series of operations are executed atomically, meaning either all operations succeed or none do.
async function createUserWithProfile(user, profile) {
try {
await db.tx(async t => {
const newUser = await t.one('INSERT INTO users(name, email) VALUES($1, $2) RETURNING *', [user.name, user.email]);
profile.userId = newUser.id;
const newProfile = await t.one('INSERT INTO profiles(bio, user_id) VALUES($1, $2) RETURNING *', [profile.bio, profile.userId]);
return { newUser, newProfile };
});
} catch (error) {
console.error('Error in transaction:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers, createUserWithProfile };
Create a new file scripts/createUserWithProfile.js
to run the createUserWithProfile
function:
const { createUserWithProfile } = require('../models/user');
createUserWithProfile(
{ name: 'Charlie', email: 'charlie@example.com' },
{ bio: 'Bio for Charlie' }
);
Run the script to create a user with a profile:
node scripts/createUserWithProfile.js
Use libraries like joi
for data validation before interacting with the database.
Install joi
:
npm install joi
Update models/user.js
to validate data:
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required()
});
async function createUser(name, email) {
const { error } = userSchema.validate({ name, email });
if (error) {
console.error('Validation error:', error.details);
return;
}
try {
const result = await db.one('INSERT INTO users(name, email) VALUES($1, $2) RETURNING *', [name, email]);
console.log(result);
} catch (error) {
console.error('Error creating user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers, createUserWithProfile };
Create a new file scripts/createValidatedUser.js
to run the createUser
function with validation:
const { createUser } = require('../models/user');
createUser('Jane Doe', 'jane.doe@example.com'); // Valid input
createUser('J', 'j@example.com'); // Invalid input
Run the script to create users:
node scripts/createValidatedUser.js
pg-promise
provides options to manage connection pools efficiently.
Update config/database.js
to include connection pool options:
const pgp = require('pg-promise')({
capSQL: true, // capitalize all generated SQL
query(e) {
console.log('QUERY:', e.query);
},
error(err, e) {
if (e.cn) {
console.error('Connection error:', err.message);
}
}
});
const db = pgp({
host: 'localhost',
port: 5432,
database: 'pg_promise_example',
user: 'your_username',
password: 'your_password',
max: 30, // use up to 30 connections
idleTimeoutMillis: 30000 // 30 seconds before a connection is closed
});
module.exports = db;
Ensure all modules use the same db
instance from config/database.js
to manage connections efficiently.
Proper error handling ensures your application can recover gracefully from failures.
Use try-catch
blocks to handle errors in asynchronous functions.
async function findUserByEmail(email) {
try {
const result = await db.oneOrNone('SELECT * FROM users WHERE email = $1', [email]);
if (!result) {
throw new Error('User not found');
}
console.log(result);
} catch (error) {
console.error('Error finding user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers, createUserWithProfile };
Create a centralized error handler to manage errors across your application.
Create a new file utils/errorHandler.js
:
function errorHandler(error, req, res, next) {
console.error('Error:', error.message);
res.status(500).json({ error: error.message });
}
module.exports = errorHandler;
Update your server setup to use the error handler:
const express = require('express');
const errorHandler = require('./utils/errorHandler');
const app = express();
// ... your routes here ...
app.use(errorHandler);
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Prepared statements improve performance and security by pre-compiling SQL queries.
const createUserQuery = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *';
async function createUser(name, email) {
try {
const result = await db.one(createUserQuery, [name, email]);
console.log(result);
} catch (error) {
console.error('Error creating user:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers, createUserWithProfile };
Use batch insertion to insert multiple rows in a single query, reducing the overhead of multiple round-trips to the database.
async function bulkInsertUsers(users) {
try {
const columns = new pgp.helpers.ColumnSet(['name', 'email'], { table: 'users' });
const query = pgp.helpers.insert(users, columns) + ' RETURNING *';
const result = await db.any(query);
console.log(result);
} catch (error) {
console.error('Error bulk inserting users:', error);
}
}
module.exports = { createUser, getAllUsers, updateUser, deleteUser, findUserByEmail, bulkInsertUsers, createUserWithProfile };
pg-promise is a powerful library for interacting with PostgreSQL databases in Node.js applications. This chapter has provided a comprehensive guide on setting up and using pg-promise, covering basic and advanced topics.Happy coding !❤️