How to deploy a MERN stack application manually
Published: 2021-12-03 | Last Updated: 2022-01-12 | ~5 Minute Read
Table of Contents
- Introduction
- Hosting
- Base Software
- MERN App Deployment
- Web Server
- SSL with Let’s Encrypt
- Closing Thoughts
Introduction
I have been working on an application recently which is based on the MERN stack and I’m now reaching a point where I would like to deploy it.
Upon looking for references online I noticed that deploying to cloud services is all the craze and with good reason, it’s fast and simple to do. As part of my learning experience I would like to experience a manual deployment to a server. I will document that process here.
Hosting
I will be deploying this to a Digital Ocean droplet. I used Ubuntu as the host OS for this deployment, below are the server versions:
root@byp:~# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
Base Software
Once the ssh access configuration and general system updates were done, I installed the following packages:
# apt install nodejs npm mongodb nginx
That should be all needed for a basic deployment, on to the application deployment.
I then added a user to the system:
root@byp:~# adduser mernuser
Adding user `mernuser' ...
Adding new group `mernuser' (1000) ...
Adding new user `mernuser' (1000) with group `mernuser' ...
Creating home directory `/home/mernuser' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for mernuser
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y
root@byp:~# usermod -aG sudo mernuser
MERN App deployment
For this specific application I did not use a public git repository, so I will be transferring the files manually, it’s just for testing so I won’t be doing continuous deployment:
scp -r mernapp/ mernuser@xxx.xxx.xxx.xxx:/home/mernuser/
I removed the node_modules
folders where appropriate so that we don’t transfer all that data, we’ll just rebuild once the files are on the target server.
Once the files are on the server from the mernapp
folder I ran the following so that all the package.json
dependencies are installed locally:
npm install
In my case I have a package.json
in two locations, the /frontend
folder and the root /
of the folder so I had to do this inside each directory to install the corresponding dependencies.
Then I did the production build of the front end:
npm run build
With this the application should be ready to be served.
Preparing NodeJS
The previous steps should leave the front end ready, now for the backend:
The following code will set the node server to run in production mode:
// Production build
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "/frontend/build")));
} else {
app.get("/", (req, res) => {
res.send("API is running...");
})
}
Starting NodeJS
I then installed a process manager for node pm2. It was fairly straight forward to use:
// Install the pm2 process manager
npm install -g pm2
// Switch to the user that owns the files on your server to start the service
su - mernuser
pm2 start "npm start"
// You can see the status with the following
pm2 list
// In case you need to look at the execution logs in real time
pm2 logs
Once I had the app running, pm2 list
reported:
mernuser@host:/var/www/html/mernapp# pm2 start "npm start"
[PM2] Starting /usr/bin/bash in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name │ mode │ ↺ │ status │ cpu │ memory │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 1 │ npm start │ fork │ 0 │ online │ 0% │ 3.4mb │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
Web Server
I will be using Nginx as a web server, first the base configuration:
server {
listen 80;
listen [::]:80;
server_name domain.com;
root /var/www/html/mernapp/frontend/build/;
index index.html index.htm index.nginx-debian.html;
location /api {
proxy_pass http://localhost:5000;
}
location / {
try_files $uri /index.html;
}
}
This configuration shows the app publicly now.
SSL with Let’s Encrypt
In order to enable SSL on the site I simply followed the instructions provided on the official certbot page for my specific configuration and executed the following command:
certbot --nginx
That came back with a successful setup and just to test renewal:
sudo certbot renew --dry-run
At this point I was able to visit my app online over HTTPS.
Closing Thoughts
Overall the process is pretty straight forward but I suppose the cloud explosion has provided additional tools and services that simplify the process even more. For simple testing like this though I think deploying like this is a bit simpler.