Skip to content

Setting up Cloud Native Postgres with Node and Express

Published: at 06:31 PM

CloudNativePG with Node.js and Express

Everyone and their mother has heard do not use kubernetes for any database. But its almost 2025 and kubernetes along with cncf(cloud native computing foundation) has come a long way. CloudNativePG along with being a kubernetes compliant way to host a database has some great benefits too.

“Additionally, CloudNativePG provides cloud native capabilities like self-healing, high availability, rolling updates, scale up/down of read-only replicas, affinity/anti-affinity/tolerations for scheduling, resource management, and so on.” - Taken from official website

In this small blog we will setup the bare minimum and usable postgres along with a node and express app to test it. This blog hopefully will be followed up by a series of in-depth tutorials on how to manage things like backups, restores and scaling.

Let’s start by installing CNPG

This will install the the CNPG operator.

kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml

Note

An operator in kubernetes for the uninitiated is just some custom definitions made from fundamental kubernetes blocks to abstract away complexity.

Database Configuration

Create your CloudNativePG cluster manifest (postgres-cluster.yaml):

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster
spec:
  instances: 3
  storage:
    size: 1Gi
  bootstrap:
    initdb:
      database: appdb
      owner: appuser
      secret:
        name: pg-credentials
---
apiVersion: v1
kind: Secret
metadata:
  name: pg-credentials
type: Opaque
stringData:
  username: appuser
  password: your-secure-password

Now apply using

kubetctl apply -f postgres-cluster.yaml

After a few seconds you will see 3 pods up. And also 3 services up

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
pg-cluster-r    ClusterIP   10.108.51.255    <none>        5432/TCP         1m
pg-cluster-ro   ClusterIP   10.103.160.182   <none>        5432/TCP         1m
pg-cluster-rw   ClusterIP   10.109.250.51    <none>        5432/TCP         1m

These 3 services each have distinct use cases, read more here For our usecase we will only be using the pg-cluster-rw service which allows read and write both.

## Node.js Application

Here's the simple `app.js` which just has one health check for database connectivity.

```javascript
const express = require('express');
const { Pool } = require('pg');

const app = express();
const port = 3000;

const pool = new Pool({
  host: 'pg-cluster-rw',
  database: 'appdb',
  user: process.env.POSTGRES_USER,
  password: process.env.POSTGRES_PASSWORD,
  port: 5432,
});

// Health check endpoint
app.get('/health', async (req, res) => {
  try {
    const result = await pool.query('SELECT NOW()');
    res.json({ status: 'healthy', timestamp: result.rows[0].now });
  } catch (err) {
    res.status(500).json({ status: 'error', error: err.message });
  }
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

Since we will need image here’s a simple Dockerfile

FROM node:22-slim

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["node", "app.js"]

Kubernetes Deployment for the node app

Deploy your application with this deployment.yaml:

apiVersion: v1
kind: Service
metadata:
  name: node-app
spec:
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      nodePort: 30000
  selector:
    app: node-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node-app
  template:
    metadata:
      labels:
        app: node-app
    spec:
      containers:
      - name: node-app
        image: node-pg-app:latest
        imagePullPolicy: Never  # Important for local images
        ports:
        - containerPort: 3000
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: pg-credentials
              key: username
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: pg-credentials
              key: password

Voila

Your app should be up accesible and you can check by curl the endpoint or if you are using minikube then runnning the command minikube service node-app

Next Steps

Consider implementing: