NodeJS Express server as a Reverse Proxy for HA (example)

Providing HTML development example of a reverse proxy created with Nodejs and Express server to connect Home Assistant from external location. This is a live example on the internet.

Required:

DNS name: in this case lavamotion.net
Let’s Encrypt certificate: previously installed.
HA external address: https://lavamotion.net:8123

Using one DNS name and different ports, this application offers the ability to connect to HTTPS and HA using the same server script in Nodejs Express. The application establishes web routes, but also works as a reverse proxy. You will connect to the server with crypto, while still being able to connect to the HA server inside the network with no certificates running inside Home Assistant.

// Contiditions for this example...
// Each server type, http, https, wsServer(s) will be listening on different ports: 80, 443, 8123 respectively in this example. 
// http and https will be on app; other websocket servers will run on their own app (app2, app3, etc).
// All http (port 80) calls are routed to https. 

// general declarations
const express = require('express');
const fs = require('fs');
const path = require('path');

// http,https server declarations
const http = require('http');
const https = require('https');
const app = express();

// websotcket proxy server declarations
const {createProxyMiddleware} = require('http-proxy-middleware');
const app2 = express();

// Let's Encrypt Certificate
const privateKey = fs.readFileSync('/etc/letsencrypt/live/lavamotion.net/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/lavamotion.net/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/lavamotion.net/chain.pem', 'utf8');

const crypto = {
	key: privateKey,
	cert: certificate,
	ca: ca
};


// ****** http, https servers ******

// redirect any page from http to https
app.enable('trust proxy')
app.use(function(request, response, next) {
    if (process.env.NODE_ENV != 'development' && !request.secure) {
       return response.redirect("https://" + request.headers.host + request.url);
    }
    next();
})

// routes
//required for Let's Encrypt certificate renewals. Must be able to download challenge files from doted directory under .well-known/acme-challenge/.
app.use(express.static(__dirname, { dotfiles: 'allow' } ));

//information about this service
app.get('/info', (req, res, next) => { 
    res.send({'message':'This is a test of the server runing https with certificate from Let\'s Encrypt.'});
});

//https default route
app.get('/', (req, res)=> {
    res.sendFile(`${__dirname}/static/index.html`);
});

//static resources
app.use(express.static('static'))
app.use(express.static('jack'))
app.use(express.static('public'))

// Starting http and https server
const httpServer = http.createServer(app);
const httpsServer = https.createServer(crypto, app);

httpServer.listen(80, () => {
	console.log('HTTP Server running on port 80');
});

httpsServer.listen(443, () => {
	console.log('HTTPS Server running on port 443');
});


// ****** websocket servers *******
// Initial connection is via https, later upgraded with connection handshake.
// Each websocket server will be isolated with its own app# and listening port.

// app2 and port 8123 is used for Home Assistant proxy, at client address https://lavamotion.net:8123.
const wsServer = https.createServer(crypto, app2);

const wsProxy = createProxyMiddleware({
    target: 'http://192.168.250.115:8123', // internal Home Assistant server, of "websocket" type.
    ws: true,
  });

app2.use(wsProxy); // add the proxy to express

wsServer.listen(8123, () => {
    console.log('Websocket Server running on port 8123');
    wsServer.on('upgrade', wsProxy.upgrade); // upgrade to websocket
});

4/23/2023 update. Added route required for Let’s Encrypt certificate renewal (line 43 of code). Certbot must be able to respond to challenges under .well-known/acme-challenge/. There is no need to respond directly through port 80 for certificate renewals, but route leading to test file via https must be found. Crontab instance must exist for renewal which will be accepted by Let’s Encrypt server anytime between 2-3 months after the creation date of the current certificate.