PostgreSQL and Services

Registered members can download the FREE Get Started App. This is the project I used to compose articles about setting up VS Code and developing Node with Express and the Embedded JavaScript (EJS) view engine.

This article describes PostgreSQL installation and connection pool implementation to integrate with Express. I use services for the data layer.

When I decided to learn Express, I found a lot of articles and AI suggestions which did not implement ES6 standards. ES6 or ECMAScript 2015 changed the landscape of JavaScript with the ability to create promises for asynchronous programming. I am familiar with ASP.NET Core and C# web applications and SQL Server databases. I developed this Express application with KenHaggerty.Com as a model. Registered members can download the FREE Get Started PostgreSQL app and Get Started MySQL app which implement this getting started with Express EJS tutorial.

Integrating a database with Express JS is half the battle of getting started. I initially implemented MySQL for this site and the Get Started App. After a couple of issues implementing a session store and Cross-Site Request Forgery, I decided to migrate this site to PostgreSQL. Rather than replacing the MySQL Get Started App, I decided to add a PostgreSQL Get Started App.

The Windows Interactive installer by EDB offers the last 6 versions of PostgreSQL. You can download the latest version available for Windows at Download PostgreSQL.

Download PostgreSQL

This installer includes the PostgreSQL server, pgAdmin, StackBuilder, and Command Line Tools. The pgAdmin is a graphical tool for managing and developing your databases. StackBuilder is a package manager for downloading and installing additional PostgreSQL tools and drivers. Command Line Tools install PostgreSQL tools such as psql, pg_isready, pgbench, clusterdb, createdb, dropdb, createuser, dropuser, pg_basebackup, pg_dump, pg_dumpall, pg_restore, reindexdb, vacuumdb, and vacuumlo. This isn't a comprehensive list, and the command-line tools installed can vary by platform.

The pgAdmin utility is an extensive graphical management tool but we can get started with the PostgreSQL for Visual Studio Code extension. From the VS Code's Extensions tab, search for and install the PostgreSQL for Visual Studio Code.

Extensions Search PostgreSQL

After the PostgreSQL extension is installed, there is a new PostgreSQL tab. From the PostgreSQL tab, click the Add New Connection button and enter the PostgreSQL installation information. Use the root username (postgres) and the installation password.

Connect to PostgreSQL Server

Expand the new connection and Databases. Right click on the postgres database and select new query.

Postgres New Query

In the query pane, enter the commands to create a new database and a database user with privileges limited to the new database.

PostgreSQL Script
CREATE DATABASE get_started;
CREATE USER get_started_user WITH PASSWORD 'P@ssw0rd';
ALTER DATABASE get_started OWNER TO get_started_user;
GRANT ALL PRIVILEGES ON DATABASE get_started TO get_started_user;

Click the Execute PostgreSQL Query button.

Postgres Execute Query

From a Terminal window in the project folder, use the Node Package Manager (npm) to install the PostgreSQL (pg) package.

PowerShell or Command Prompt
npm install pg

Use the .env file to pass the database configuration to the application's process.env object.

.env
DB_CONFIGURED=true
DB_HOST=localhost
DB_PORT=5432
DB_USER=get_started_user
DB_PASSWORD=P@ssw0rd
DB_DATABASE=get_started

You load the variables to the runtime process.env object with the loadEnvFile() method at the very start of server.js.

server.js
process.loadEnvFile(); // Loads from .env by default

Add a new root directory named Services and add a new file named connection-pool.mjs. Import the Pool object from the pg package, then create, test and export the new configured pool.

connection-pool.mjs
import { Pool } from 'pg';

export const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_DATABASE,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT, 
  // Pool-specific settings
  max: 10,                    // Maximum connections in the pool
  idleTimeoutMillis: 30000,   // Close idle connections after 30 seconds
  connectionTimeoutMillis: 2000, // Fail fast if connection takes > 2 seconds
});

// Test database connection
pool.connect((err, client, release) => {
  if (err) {
    return console.error('Error acquiring client', err.stack);
  }
  client.query('SELECT NOW()', (err, result) => {
    release();
    if (err) {
      return console.error('Error executing query', err.stack);
    }
    console.log(`Connected to database: ${process.env.DB_DATABASE}!`);
  });
});

Add a settings-service.mjs file to the Services directory. Import the connection-pool.mjs to perform database queries. If you use the pool's shortcut pool.query() method, the connection is automatically acquired, used for the single query, and returned to the pool. This is the easiest and recommended approach for simple, one-off queries.

The Get Started PostgeSQL app creates a Settings table if DB_CONFIGURED=true.

settings-service.mjs
import { pool } from './connection-pool.mjs';

const SettingsService = {};

// Function to create the settings table if not exists
SettingsService.initialize = async () => {
  await pool.query(
    `CREATE TABLE IF NOT EXISTS settings (
      id int NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1000),
      setting varchar(256) NOT NULL,
      value text NOT NULL,
      updated timestamptz NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (id),
      CONSTRAINT ux_settings_setting UNIQUE (setting)
    );`
  );
};

// Function to create a new setting
SettingsService.create = async (newSetting) => {
  const result = await pool.query(
    'INSERT INTO settings (setting, value) VALUES ($1, $2) RETURNING *',
    [newSetting.setting, newSetting.value]
  );
  return result.rows[0];
};

// Function to find a setting by ID
SettingsService.findById = async (id) => {
  const result = await pool.query('SELECT * FROM settings WHERE id = $1', [id]);
  return result.rows.length > 0 ? result.rows[0] : null;
};

// Function to find the value by setting
SettingsService.findValueBySetting = async (setting) => {
  const result = await pool.query('SELECT value FROM settings WHERE setting = $1', [setting]);
  return result.rows.length > 0 ? result.rows[0].value : '';
};

// Function to find all settings
SettingsService.findAll = async () => {
  const result = await pool.query('SELECT * FROM settings');
  return result.rows;
};

// Function to update a setting
SettingsService.update = async (id, updatedSetting) => {
  const result = await pool.query(
    'UPDATE settings SET setting = $1, value = $2, updated = $3 WHERE id = $4 RETURNING *',
    [updatedSetting.setting, updatedSetting.value, updatedSetting.updated, id]
  );
  return result.rows.length > 0;
};

// Function to remove a setting
SettingsService.remove = async (id) => {
  const result = await pool.query('DELETE FROM settings WHERE id = $1 RETURNING *', [id]);
  return result.rows.length > 0;
};

export default SettingsService;

Import the SettingsService where needed.

import SettingsService from '../services/settings-service.mjs';

Then you can call SettingsService functions.

const contactName = await SettingsService.findValueBySetting('Contact-Name');
Created: 2/27/26