- vừa được xem lúc

Blog#177: 🤔MAYBE YOU DON'T KNOW - 👌Using Node-Postgres in Node.js Express✨

0 0 12

Người đăng: NGUYỄN ANH TUẤN

Theo Viblo Asia

177

Hi, I'm Tuan, a Full-stack Web Developer from Tokyo 😊. Follow my blog to not miss out on useful and interesting articles in the future.

In this article, we'll walk you through a step-by-step guide on using node-postgres with Node.js Express. We'll apply it to a real project, making it easy to understand, flexible, and scalable. We'll also implement some important mechanisms like singleton, repository, query on two databases for read and write, and a reconnect mechanism when disconnected from the database.

Introduction to Node-Postgres

Node-Postgres is a popular PostgreSQL client library for Node.js. It allows you to interact with a PostgreSQL database easily and efficiently. With its simple API, you can execute queries, manage transactions, and utilize connection pooling for better performance.

Source Structure

Here's an overview of the source structure for this project:

node-postgres-demo/
|-- initdb/
| |-- init.sql
|-- repositories/
| |-- userRepository.js
|-- .env
|-- db.js
|-- docker-compose.yml
|-- Dockerfile
|-- index.js
|-- package.json

Setting Up the Project

Before diving into the code, make sure you have the following installed:

  • Node.js (14.x or higher)
  • PostgreSQL (9.6 or higher)

If you have docker, you can also run this project without these.

First, create a new directory for your project and navigate to it:

mkdir node-postgres-demo
cd node-postgres-demo

Initialize a new Node.js project and install the necessary dependencies:

npm init -y
npm install express pg dotenv
npm install nodemon --save-dev

Creating a Singleton Connection Pool

A connection pool is a cache of database connections maintained to improve performance. To create a singleton connection pool, we'll use the pg package and the Singleton design pattern.

Create a new file called db.js and paste the following code:

const { Pool } = require('pg'); class Singleton { constructor(connectionString) { if (!Singleton.instances) { Singleton.instances = {}; } if (!Singleton.instances[connectionString]) { const pool = this.createPool(connectionString); Singleton.instances[connectionString] = pool; } } getInstance(connectionString) { return Singleton.instances[connectionString]; } createPool(connectionString) { const pool = new Pool({ connectionString }); pool.on('error', (err, client) => { console.error('Unexpected error on idle client: ', connectionString); setTimeout(() => { console.log('Attempting to reconnect...: ', connectionString); Singleton.instances[connectionString] = this.createPool(connectionString); }, 5000); }); pool.on('connect', () => { console.log('Connected to the database'); }); return pool; }
} module.exports = Singleton;

Now, you can use the Singleton class to get a connection pool instance in other parts of your application.

Implementing the Repository Pattern

The repository pattern helps abstract the data access logic, making it more maintainable and testable. Create a new folder called repositories and create a new file inside it called userRepository.js:

const Singleton = require('../db'); class UserRepository { constructor() { this.readPool = new Singleton(process.env.READ_DATABASE_URL).getInstance(process.env.READ_DATABASE_URL); this.writePool = new Singleton(process.env.WRITE_DATABASE_URL).getInstance(process.env.WRITE_DATABASE_URL); } async getUserById(id) { const res = await this.readPool.query('SELECT * FROM users WHERE id = $1', [id]); return res.rows[0]; } async createUser(user) { const res = await this.writePool.query('INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [user.name, user.email]); return res.rows[0]; } async updateUser(id, user) { const res = await this.writePool.query('UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *', [user.name, user.email, id]); return res.rows[0]; } async deleteUser(id) { const res = await this.writePool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]); return res.rows[0]; }
} module.exports = UserRepository;

Environment Configuration

Create a .env file in the project root directory to store the environment variables:

READ_DATABASE_URL=postgres://user:password@db_read:5432/db_read
WRITE_DATABASE_URL=postgres://user:password@db_write:5432/db_write
PORT=3000

Make sure to replace your_read_db_connection_string and your_write_db_connection_string with your actual PostgreSQL connection strings.

Package.json and Entry Point

You can also modify the package.json file in the project root directory with the following content:

{ "name": "node-postgres-demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "dotenv": "^10.0.0", "express": "^4.17.1", "pg": "^8.7.1" }
}

If you have modified the package.json file re-run this command:

npm i

Create an index.js file in the project root directory:

const express = require('express');
const UserRepository = require('./repositories/userRepository');
require('dotenv').config(); const app = express();
app.use(express.json());
const userRepository = new UserRepository(); app.get('/users/:id', async (req, res) => { try { const user = await userRepository.getUserById(req.params.id); if (user) { res.json(user); } else { res.status(404).send('User not found'); } } catch (error) { console.error(error); res.status(500).send('Internal server error'); }
}); app.post('/users', async (req, res) => { try { const newUser = await userRepository.createUser(req.body); res.status(201).json(newUser); } catch (error) { console.error(error); res.status(500).send('Internal server error'); }
}); app.put('/users/:id', async (req, res) => { try { const updatedUser = await userRepository.updateUser(req.params.id, req.body); if (updatedUser) { res.json(updatedUser); } else { res.status(404).send('User not found'); } } catch (error) { console.error(error); res.status(500).send('Internal server error'); }
}); app.delete('/users/:id', async (req, res) => { try { const deletedUser = await userRepository.deleteUser(req.params.id); if (deletedUser) { res.json(deletedUser); } else { res.status(404).send('User not found'); } } catch (error) { console.error(error); res.status(500).send('Internal server error'); }
}); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`);
});

Now you have a complete Node.js Express application using node-postgres, with a singleton connection pool, the repository pattern, querying multiple databases for read and write operations, and a reconnect mechanism when disconnected from the database.

To run the application, use the following command if you have available the Postgres DB if not then the next step:

npm start

Docker Configuration and Initialize Sample Data

To initialize the sample data for the users table, we can use a SQL script that runs when the PostgreSQL container starts. Create a new folder called initdb in your project's root directory, and inside the initdb folder, create a file called init.sql with the following content:

CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL
);
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com');

This script will create a users table if it doesn't exist and insert three sample users into the table.

Let's create a new file called Dockerfile and paste the following code:

FROM node:16 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD ["npm", "start"]

Now, create a new file called docker-compose.yml and paste the following code:

version: '3.9' services: app: build: . ports: - '${PORT}:${PORT}' depends_on: - db_read - db_write environment: - READ_DATABASE_URL=${READ_DATABASE_URL} - WRITE_DATABASE_URL=${WRITE_DATABASE_URL} volumes: - .:/app db_read: image: postgres:12-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=password - POSTGRES_DB=db_read volumes: - ./initdb:/docker-entrypoint-initdb.d db_write: image: postgres:12-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=password - POSTGRES_DB=db_write volumes: - ./initdb:/docker-entrypoint-initdb.d

To run the application with Docker Compose, use the following command:

docker-compose up

When the containers start, the users table will be created and populated with sample data. And the node application is also started in the container.

Conclusion

In this article, we've covered how to use node-postgres with Node.js Express, implementing a singleton connection pool, the repository pattern, querying multiple databases for read and write operations, and a reconnect mechanism. By following this guide, you can build a flexible, scalable, and maintainable application using node-postgres.

And Finally

As always, I hope you enjoyed this article and learned something new. Thank you and see you in the next articles!

If you liked this article, please give me a like and subscribe to support me. Thank you. 😊

Ref

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tự build cho mình một hệ thống BADGE MARKDOWN siêu xịn xò - #1

Chào mọi người, lại là mình và cái serie "Something thú vị" của mình đây, serie tổng hợp những kiến thức mình cảm thấy thú vị ở trên google, github, bla bla... Qua đó chia sẻ những project nho nhỏ, thích hợp để mọi người làm cho vui hoặc relax hoặc giải trí. https://github.com/weebNeedWeed/custom-ba

0 0 20

- vừa được xem lúc

Node.js Tutorial: Phần 7 - Sử dụng EJS làm Template Engine trong Express

Tạo một project Express với express-generator. Cách nhanh nhất để tạo một project ExpressJS là sử dụng express-generator.

0 0 26

- vừa được xem lúc

File upload với Multer, Nodejs và Express

Khi một web client tải một tệp lên máy chủ, nó thường được gửi qua một biểu mẫu và được mã hóa dưới dạng dữ liệu multipart/form-data. Multer là một middleware cho Express và Nodejs giúp dễ dàng xử lý

0 0 99

- vừa được xem lúc

Bước đầu làm quen với NodeJS và Socket.io

Chào mọi người. Đôi điều chia sẻ trước khi đi vào nội dung chính của bài viết. Hôm nay là bài viết đầu tiên mình được viết trên Viblo.asia.

0 0 31

- vừa được xem lúc

CRUD với NodeJs, Express và MongoDB

1. Cài đặt môi trường. . B1: Tạo một thư mục dự án.

0 0 61

- vừa được xem lúc

Xây dựng app chat realtime với VueJS - NodeJS - Express - SocketIO

Mở đầu. Từ lâu mình đã ấp ủ tự mình làm được một app chat để chat với bạn bè.

0 0 126