點燈坊

失くすものさえない今が強くなるチャンスよ

Nginx + Express + PostgreSQL as Microservice

Sam Xiao's Avatar 2021-11-20

If we want to use Microservice architecture, we will build Nginx, Express, PostgreSQL images first, and then use Docker Compose to run Nginx, Express, PostgreSQL at once.

Version

Vue 3.2
Nginx 1.21.3
Express 4.17.1
PostgreSQL 14.0

Architecture

postgre000

Nginx and Express are all in Docker, and they expose ports 7979 and 8080 outside of Docker, but PostgreSQL is only available inside Docker.

Vue

postgre001

Vue runs successfully on port 7979.

App.vue

<script setup>
import axios from 'axios'

const API_SVR = import.meta.env.VITE_API_SVR
let articles = $ref ([])

let onClick = async _ => {
  let { data } = await axios.get (`${API_SVR}/api/articles`)
  articles = data
}
</script>

<template>
  <button @click="onClick">Get Articles</button>
  <ul>
    <li v-for="x in articles">{{ x.id }}:{{ x.title }} / {{ x.content }}</li>
  </ul>
</template>

Line 4

const API_SVR = import.meta.env.VITE_API_SVR

Read VITE_API_SVR environment variable in .env.

LIne 5

let articles = $ref ([])

Declare articles reactive variable with default value [].

Line 7

let onClick = async _ => {
  let { data } = await axios.get (`${API_SVR}/api/articles`)
  articles = data
}

Call http://localhost:8080/api/articles to get articles.

Environment Variable

.env

VITE_API_SVR=http://localhost:8080
DB_SVR=Postgres
VUE_VER=0.0.0
EX_VER=0.0.0
NGINX_PORT=7979
EX_PORT=8080
HOST_DIR=.data
POSTGRES_PORT=5432
POSTGRES_DB=DBLab
POSTGRES_USER=admin
POSTGRES_PASSWORD=12345

Environment variable for Vue and Docker Compose :

  • VITE_API_SVR : URL for API server, which Vue uses
  • DB_SVR : database server, which Express uses
  • VUE_VER : Vue build version, which Docker Compose uses
  • EX_VER : Express build version, which Docker Compose uses
  • NGINX_PORT : Nginx exposed port, which Docker Compose uses
  • EX_PORT : Express exposed port, which Docker Compose uses
  • HOST_DIR : host data directory mounted to Postgres
  • POSTGRES_PORT : Postgre exposed port, which Express uses
  • POSTGRES_DB : Postgre database, which Express and Postgre use
  • POSTGRES_USER : Postgre user, which Express and Postgre use
  • POSTGRES_PASSWORD : Postgre password, which Express and Postgre use

Dockerfile

FROM nginx:alpine
COPY dist /usr/share/nginx/html
  • Build Vue image by Nginx image
  • Copy all files in dist directory to /usr/share/nginx/html in image

Docker Compose

docker-compose.yml

version: "3"
services:
  nginx:
    image: nginx:${VUE_VER}
    container_name: Nginx
    ports:
      - ${NGINX_PORT}:80
    restart: always 
  express:
    image: express:${EX_VER}
    container_name: Express
    ports:
      - ${EX_PORT}:8080
    environment:
      - DB_SVR=${DB_SVR}
      - DB_PORT=${POSTGRES_PORT}
      - DB_USER=${POSTGRES_USER}
      - DB_PASSWORD=${POSTGRES_PASSWORD}
      - DB_DATABASE=${POSTGRES_DB}
    restart: always
  postgres:
    image: postgres:latest
    container_name: Postgres
    volumes:
      - ${HOST_DIR}:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    restart: always

Line 3

nginx:
  image: nginx:${VUE_VER}
  container_name: Nginx
  ports:
    - ${NGINX_PORT}:80
  restart: always 
  • Run container by nginx image with version
  • ports : map port by NGINX_PORT environment variable with internal port 80

Line 9

express:
  image: express:${EX_VER}
  container_name: Express
  ports:
    - ${EX_PORT}:8080
  environment:
    - DB_SVR=${DB_SVR}
    - DB_PORT=${POSTGRES_PORT}
    - DB_USER=${POSTGRES_USER}
    - DB_PASSWORD=${POSTGRES_PASSWORD}
    - DB_DATABASE=${POSTGRES_DB}
  restart: always
  • Run container by the express image with version

  • ports : map port by EX_PORT environment variable with internal port 8080

  • environment :

    • DB_SVR : transfer DB_SVR Docker Compose environment variable to DB_SVR Express environment variable

    • DB_PORT : transfer POSTGRES_PORT Docker Compose environment variable to DB_PORT Express environment variable

    • DB_USER : transfer POSTGRES_USER Docker Compose environment variable to DB_USER Express environment variable

    • DB_PASSWORD : transfer POSTGRES_PASSWORD Docker Compose environment variable to DB_PASSWORD Express environment variable

    • DB_DATABASE : transfer POSTGRES_DB Docker Compose environment variable to DB_DATABASE Express environment variable

Line 21

postgres:
  image: postgres:latest
  container_name: Postgres
  volumes:
    - ${HOST_DIR}:/var/lib/postgresql/data
  environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=${POSTGRES_DB}
  restart: always
  • Run container by latest postgres image
  • volumns : map host directory to store data
  • environment :
    • POSTGRES_USER : transfer POSTGRES_USER Docker Compose environment variable to POSTGRES_USER PostgreSQL environment variable
    • POSTGRES_PASSWORD : transfer POSTGRES_PASSWORD Docker Compose environment variable to POSTGRES_PASSWORD PostgreSQL environment variable
    • POSTGRES_DB : transfer POSTGRES_DB Docker Compose environment variable to POSTGRES_DB PostgreSQL environment variable

NPM Config

package.json

{
  "name": "vue-express-postgre",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "serve": "vite preview",
    "build-vue": "vite build",
    "build-nginx": "yarn build-vue && docker build -t nginx:$npm_package_version .",
    "build-express": "docker build -t express:$npm_package_version ./express",
    "build": "yarn build-nginx && yarn build-express"
  },
  "dependencies": {
    "axios": "^0.24.0",
    "vue": "^3.2.6"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.0",
    "@vue/compiler-sfc": "^3.0.5",
    "vite": "^2.5.1"
  }
}

LIne 7

"build-vue": "vite build",

Build Vue to the dist directory.

Line 8

"build-nginx": "yarn build-vue && docker build -t nginx:$npm_package_version .",

Build Vue to the dist directory and make Vue as Nginx image.

LIne 9

"build-express": "docker build -t express:$npm_package_version ./express",

Build Express to image.

Line 10

"build": "yarn build-nginx && yarn build-express"

Build Nginx and Express image at once.

Express

postgres002

Express runs successfully on port 8080.

App.js

import express from 'express'
import { config } from 'dotenv'
import cors from 'cors'
import Knex from 'knex'

if (process.env.NODE_ENV !== 'production')
  config ()

let app = express ()
app.use (cors ())
app.use (express.json ())

let knex = Knex ({
  client: 'pg',
  connection: {
    host: process.env.DB_SVR,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE
  },
  searchPath: ['public']
})

app.get ('/api/articles', async (req, res) => {
  let { rows } = await knex.raw ('SELECT * FROM articles')
  res.send (rows)
})

app.listen (8080, _ => console.log ('Express listen on port: 8080'))

Line 6

if (process.env.NODE_ENV !== 'production')
  config ()

If not in production mode, Express will read the environment variable from .env in the express directory.

Line 13

let knex = Knex ({
  client: 'pg',
  connection: {
    host: process.env.DB_SVR,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE
  },
  searchPath: ['public']
})

Connect to Postgres by Knex, all environment variables are derived from Docker Compose.

Line 25

app.get ('/api/articles', async (req, res) => {
  let { rows } = await knex.raw ('SELECT * FROM articles')
  res.send (rows)
})

Use knex. raw to run raw SQL to Postgres.

Environment Variable

.env

DB_SVR=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=12345
DB_DATABASE=DBLab

Environment variable used by development mode.

Dockerfile

FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /usr/src/app
COPY package.json ./package.json
RUN yarn install
COPY app.js .
CMD [ "node", "app.js" ]
  • Build Express image by Node image
  • Copy app.js to image
  • Set NODE_ENV environment variable to production to indicate production mode

NPM Config

{
  "type": "module",
  "name": "express",
  "version": "0.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "knex": "^0.95.13",
    "pg": "^8.7.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.14"
  },
  "scripts": {
    "dev": "nodemon app.js"
  }
}

Line 18

"dev": "nodemon app.js"

Run Express in development mode.

PostgreSQL

Environment Variable

.env

HOST_DIR=.data
POSTGRE_PORT=5432
POSTGRE_DB=DBLab
POSTGRE_USER=admin
POSTGRE_PASSWORD=12345

Environment variable used by development mode.

Docker Compose

docker-compose.yml

version: "3"

services:
  postgres:
    image: postgres:latest
    container_name: Postgres
    volumes:
      - ${HOST_DIR}:/var/lib/postgresql/data
    ports:
      - ${POSTGRES_PORT}:5432
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

Postgres container used by development mode.

Development

$ yarn dev

Run yarn dev in the root directory to run the dev server for Vue.

$ yarn dev

Run yarn dev in the express directory to run Express by Node.

$ docker-compose up -d

run docker-compose up -d in the postgres directory to run Postgres.

Production

$ yarn build

Run yarn build to build Nginx image and Express image at once.

$ docker-compose up -d

Run docker-compose up -d in the root directory to run Nginx, Express, Postgres at once.

Conclusion

  • Nginx and Express expose their ports outside of Docker, but Postgres only expose its port inside of Docker, which Express only uses
  • In the recommended architecture in this post, we can both have development mode and production mode at the same time