Express is one of the popular NodeJS frameworks to create web servers. Going through Getting started page is enough to learn about Express but to make it into production a few best practices are mandatory. In this post, we implement a simple Express web server with all the best practices for production. It will contain following features:
Getting Started
Prerequisites
Setup
Run following commands to create a simple Express server:
PERFORMANCE
Process Manager
RULE: Never run your app with ‘npm start’ in production
If our web app crashes, we don’t want our application to be offline until we manually restart our web app.
Using PM2 as process manager will handle following cases:
- Restarts our app if it crashes
- Reloads with zero downtime when invoked
- Built-in load balancer
PM2 Setup
- Run following cmd to install PM2:
- Install node-cmd to launch our app in a console:
- Add file named
scripts.js
at our app’s root with contents:
- Add file named
ecosystem.config.js
(pm2 config file) at app’s root with following contents:
- Execute any of the following cmds to start our app using PM2 according to environment:
For Dev: pm2 start ecosystem.config.js
For Test: pm2 start ecosystem.config.js --env test
For Production: pm2 start ecosystem.config.js --env production
- Refer to PM2 CheatSheet to learn all the commands for managing our server running on PM2
Compression
NOTE: ONLY REQUIRED WHEN NOT USING REVERSE PROXY
Gzip compressing can greatly decrease the size of the response body and hence increase the speed of a web app. Use the compression middleware for gzip compression in your Express app.
- Install compression module and import in
app.js
:
- Add compression middleware in
app.js
:
Reverse Proxy
- A reverse proxy sits in front of a web app and performs supporting operations on the requests, apart from directing requests to the app.
- It can handle error pages, compression, caching, serving files, and load balancing among other things.
- Handing over tasks that do not require knowledge of application state to a reverse proxy frees up Express to perform specialized application tasks.
(COMING UP…)
Dockerization
- Dockerization can make it easy to ship our app to production
- It can auto start our app on server boot
(COMING UP…)
SECURITY
SECRETS
Setting up Secrets
-
We have to keep certain variables, like PORT, DB URL etc., configurable. We can do this by adding config.js and storing the values there. But this approach makes a compiled config.
-
In order to achieve runtime config, we need to store our config values as environmental variables and load them to config.js. A simple dotenv approach is fair enough.
-
But to load configs based upon the environment, it’s better to user
.env-cmdrc
from env-cmd module
Run following command to install env-cmd:
Create .env-cmdrc
file at the root of the project with contents in following format:
You can also add configs for staging environment if required
In package.json
change the scripts to following:
Now you can run any of the following commands(according to the environment) to start your application. All your configs will be loaded to environmental variables:
Reading Secrets
- You can directly read the config values set in
.env-cmdrc
in any of your project’s JS file as:
- But we have to remember the exact name of the config in
.env-cmdrc
and use them appropriately - To read the config values in a comfortable manner, add a new folder
config
and create a fileconfig.js
and add following content to the file:
- You can now get the config values anywhere in your project as simply as:
Securing Secrets
RULE: Never commit your secrets file to source control. Make sure you add
.env-cmdrc
to.gitignore
We should not expose our secrets file to public. There are three approaches to do this:
- Store the secrets file on your own premises somewhere secure
- Commit a prototype of your secrets file with dummy values as a reference
- Encrypt the secrets file with a password and commit the encrypted file
OPTIONAL: First two approaches are straight forward. I’m going to implement the 3rd approach using makefile. I’ll be using OpenSSL to encrypt the file.
- Add file named
makefile
to root of the project with following contents:
- Execute following command to encrypt the secrets file:
- To decrypt:
- Enter password when prompted to encrypt/decrypt the file
Migrating to HTTPS
npx-generator creates a basic HTTP server for us. But we must always use HTTPS for our server while in production.
- Get yourself a SSL Certificate for your server and store the certificate files(cert.pem & private key file) in a new folder named
certificates
in your project’s root.
RULE: Never commit your certificates folder to source control. Make sure you add
certificates/
to.gitignore
-
Generating Self-signed Certificate:
ONLY FOR DEVELOPMENT PURPOSES. SKIP THIS STEP FOR PRODUCTION AND MAKE SURE YOU HAVE A CA SIGNED SSL CERTIFICATE
To generate a self-signed certificate, run following command in
certificates
folder:
The above command will generate two files: cert.pem
and private.key
. The private key will be unprotected without any passphrase & the certificate will be registered for the domain: localhost
and valid for 365 days.
- Add following imports to
bin/www
file:
-
replace each of the following striped lines accordingly
var port = normalizePort(process.env.PORT || ‘3000’);
app.set(‘port’, port);
var server = http.createServer(app);
server.listen(port);
- Now run your server and check at corresponding https URL
Helmet
RULE: Never expose X-Powered-By header
Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately.
Helmet is actually just a collection of smaller middleware functions that set security-related HTTP response headers:
- csp sets the Content-Security-Policy header to help prevent cross-site scripting attacks and other cross-site injections.
- hidePoweredBy removes the X-Powered-By header.
- hsts sets Strict-Transport-Security header that enforces secure (HTTP over SSL/TLS) connections to the server.
- ieNoOpen sets X-Download-Options for IE8+.
- noCache sets Cache-Control and Pragma headers to disable client-side caching.
- noSniff sets X-Content-Type-Options to prevent browsers from MIME-sniffing a response away from the declared content-type.
- frameguard sets the X-Frame-Options header to provide clickjacking protection.
- xssFilter sets X-XSS-Protection to enable the Cross-site scripting (XSS) filter in most recent web browsers.
Setup
- Install helmet module and import it in
app.js
- Add helmet to express in
app.js
Securing Session Cookies
RULE: Always enable secure & httpOnly flags for all cookies
- Install express-session module and import in
app.js
- Add session middleware in
app.js
Securing Dependencies
RULE: Always make sure you run
npm audit
and there are 0 vulnerabilities whenever you install a new npm module