Angular + Node.js on AWS - How to Deploy a MEAN Stack App to Amazon EC2
In this tutorial we're going to setup a production ready web server from scratch on the Amazon EC2 (Elastic Compute Cloud) service, then deploy a custom MEAN Stack application to it that supports user registration and authentication.
What is a MEAN Stack App?
A MEAN Stack application is made up of a front-end app built with Angular that connects to a back-end api built with Node.js + Express + MongoDB, hence the name MEAN Stack (Mongo, Express, Angular, Node). Other variations of the stack include the MERN Stack that has a React front-end, and the MEVN Stack that has a Vue.js front-end.
Scope of this tutorial
This tutorial will be focused on setting up the server on AWS EC2, then deploying and configuring the front-end and back-end pieces of the MEAN stack app to work together. For more in-depth information about the Angular app or Node.js api used in this post, check out the following tutorials which cover them in detail:
- Angular 8 - User Registration and Login Tutorial & Example
- NodeJS + MongoDB - Simple API for Authentication, Registration and User Management
Tutorial Contents
- Create a new Ubuntu server on AWS EC2
- Connect to Ubuntu EC2 instance via SSH
- Setup server with Node.js + MongoDB + NGINX
- Deploy Node.js + MongoDB back-end API
- Build and Deploy Angular Front-end app
- Configure NGINX to serve API and front-end
- Test the MEAN Stack app in a browser
Create a new Ubuntu Server on AWS EC2
Before doing anything we need a server that we can work on, follow these steps to spin up a new Ubuntu 18.04 server instance on AWS EC2.
- Sign into the AWS Management Console at https://aws.amazon.com/console/. If you don't have an account yet click the "Create a Free Account" button and follow the prompts.
- Go to the EC2 Service section.
- Click the "Launch Instance" button.
- Choose AMI - Check the "Free tier only" checkbox, enter "Ubuntu" in search box and press enter, then select the "Ubuntu Server 18.04" Amazon Machine Image (AMI).
- Choose Instance Type - Select the "t2.micro" (Free tier eligible) instance type and click "Configure Security Group" in the top menu.
- Configure Security Group - Add a new rule to allow HTTP traffic then click "Review and Launch".
- Review - Click Launch
- Select "Create a new key pair", enter a name for the key pair (e.g. "my-aws-key") and click "Download Key Pair" to download the private key, you will use this to connect to the server via SSH.
- Click "Launch Instances", then scroll to the bottom of the page and click "View Instances" to see details of the new Ubuntu EC2 instance that is launching.
Connect to Ubuntu EC2 Instance via SSH
Once the EC2 instance reaches a running state you can connect to it via SSH using the private key downloaded in the previous step.
Connect from Mac OSX
- Open a terminal window and update the permissions of the private key file with the command
chmod 400 <path-to-key-file>
e.g.chmod 400 ~/Downloads/my-aws-key.pem
, the key must not be publicly viewable for SSH to work. - Copy the "Public DNS (IPv4)" property from the instance description tab in the AWS Console, then connect to the instance from the terminal window with the command
ssh -i <path-to-key-file> ubuntu@<domain name>
e.g.ssh -i ~/Downloads/my-aws-key.pem [email protected]
- Enter
yes
to the prompt "Are you sure you want to continue connecting (yes/no)?" to add the url to your list of known hosts.
Connect from Windows
If you're using Windows you can connect to your instance via SSH using the PuTTY SSH client, for instructions see Connect Using PuTTY in the AWS docs.
Setup Web Server with Node.js + MongoDB + NGINX
The below command executes a script to automatically setup and configure a production ready MEAN Stack web server on Ubuntu that includes Node.js, MongoDB, PM2, NGINX and UFW.
For more details about how the script works see Setup Node.js + MongoDB Production Server on Ubuntu.
While connected to the new AWS EC2 instance in the terminal window, run the following command:
curl https://gist.githubusercontent.com/cornflourblue/f0abd30f47d96d6ff127fe8a9e5bbd9f/raw/e3047c9dc3ce8b796e7354c92d2c47ce61981d2f/setup-nodejs-mongodb-production-server-on-ubuntu-1804.sh | sudo bash
For instructions on how to securely connect to the remote MongoDB server from your local machine using Mongo Shell or MongoDB Compass see Connect to remote MongoDB on AWS EC2 simply and securely via SSH tunnel.
Deploy Node.js + MongoDB Back-end API
Follow these steps to setup the Node.js API on the server and configure NGINX to enable access to it.
- Clone the Node.js + MongoDB API project into the
/opt/back-end
directory with the commandsudo git clone https://github.com/cornflourblue/node-mongo-registration-login-api /opt/back-end
- Navigate into the back-end directory and install all required npm packages with the command
cd /opt/back-end && sudo npm install
- Start the API using the PM2 process manager with command
sudo pm2 start server.js
The API is now running on Node.js under the PM2 process manager and listening on port 4000. Only port 80 (HTTP) is publicly accessible on the server so we can't hit the API yet, this will be possible after we've configured NGINX as a reverse proxy to pass through HTTP traffic to the api (more on this shortly).
Build and Deploy the Angular Front-end app
Follow these steps to build the Angular application on your local machine and then deploy it to the AWS server.
Build the Angular App
- Clone the Angular project to a folder on your local machine with the command
git clone https://github.com/cornflourblue/angular-8-registration-login-example.git
. If you don't have the Git CLI installed it can be downloaded from https://git-scm.com/downloads. - Navigate into the cloned directory and install all required node packages with the command
npm install
. If you need to install Node.js (which includes npm) it can be downloaded from https://nodejs.org/. - Update the app to use real backend API:
- Open the Angular app module file (
/src/app/app.module.ts
) in a text editor. - Delete the following lines from the file to remove the fake backend that the Angular app uses by default:
// used to create fake backend import { fakeBackendProvider } from './_helpers';
// provider used to create fake backend fakeBackendProvider
- Open the Angular app module file (
- Configure the path to API:
- Open the webpack config file (
/webpack.config.js
) in a text editor. - Change the
apiUrl
config property to'/api'
like below:// global app config object config: JSON.stringify({ apiUrl: '/api' })
- Open the webpack config file (
- Build the Angular app with the command
npm run build
Deploy the Angular App from Mac OSX
- Connect to the server via SSH (instructions above) and create a new folder for the front end app with the command
sudo mkdir /opt/front-end
- Change the owner of the folder to the ubuntu user and group with the command
sudo chown ubuntu:ubuntu /opt/front-end
. This is to allow us to transfer the front end files in the next step. - Transfer the compiled app to the server via SSH with the command:
scp -i <path-to-key-file> -r <path-to-local-dist-folder>/* ubuntu@<domain name>:/opt/front-end
E.g.scp -i ~/Downloads/my-aws-key.pem -r ~/Downloads/angular-8-registration-login-example/dist/* [email protected]:/opt/front-end
Deploy the Angular App from Windows
This assumes you've already followed the instructions on how to connect from Windows using PuTTY in the AWS docs.
- Connect to the server via SSH (instructions above) and create a new folder for the front end app with the command
sudo mkdir /opt/front-end
- Change the owner of the folder to the ubuntu user and group with the command
sudo chown ubuntu:ubuntu /opt/front-end
. This is to allow us to transfer the front end files in the next step. - Transfer the compiled app to the server via SSH using the PuTTY Secure Copy client with the command:
pscp -i <path-to-key-file> -r <path-to-local-dist-folder>\* ubuntu@<domain name>:/opt/front-end
E.g.pscp -i C:\Downloads\my-aws-key.ppk -r C:\Downloads\angular-8-registration-login-example\dist\* [email protected]:/opt/front-end
Configure NGINX to serve the Node.js API and Angular front-end
Since our MEAN Stack application is made up of two separate projects that both need to be accessed via the same port (HTTP on port 80), we're going to use NGINX as our public facing web server to receive requests for both the front-end and back-end, and decide where to send each request based on its path. Requests beginning with the path /api/*
will be proxied through to the Node.js api running on port 4000, while other requests will serve the Angular front-end app and associated files (js/css/images).
Follow these steps to configure NGINX for the MEAN stack app.
- Delete the default NGINX site config file with the command
sudo rm /etc/nginx/sites-available/default
- Launch the nano text editor to create an new default site config file with
sudo nano /etc/nginx/sites-available/default
- Paste in the following config:
server { charset utf-8; listen 80 default_server; server_name _; # angular app & front-end files location / { root /opt/front-end; try_files $uri /index.html; } # node api reverse proxy location /api/ { proxy_pass http://localhost:4000/; } }
- Save the file by pressing
ctrl
+x
and selectingYes
to save. - Restart NGINX with the command
sudo systemctl restart nginx
NGINX Config Reference
server { ... }
defines a server block which contains the configuration for a virtual server within NGINX.
charset utf-8;
uses the charset directive to configure the virtual server to send all content with UTF-8 encoding, and importantly prevents any unicode characters in your javascript from being converted before being sent to the browser which can cause errors (e.g. invalid regular expression errors).
listen 80 default_server;
uses the listen directive to configure the virtual server to accept requests on port 80
and sets it as the default virtual server on this NGINX server.
server_name _;
uses the server_name directive to set the server name to an underscore (_
) to make this server block a catch-all block that matches any domain name that doesn't match another more specific server block. Since this example has only one server block it will match all domain names.
location / { ... }
defines a location block which contains the configuration for requests that have a URI beginning with a forward slash (/
), unless the request URI matches another more specific location block.
root /opt/front-end/dist;
uses the root directive to set the root directory to the front end dist folder (/opt/front-end/dist
) for requests matching this location block.
try_files $uri /index.html;
uses the try_files directive to first check for the existence of a file matching the request URI ($uri
) and returning it if there is one. If no file matches the request URI then it defaults to returning /index.html
.
location /api/ { ... }
defines a location block which contains the configuration for requests that have a URI beginning with /api/
.
proxy_pass http://localhost:4000/;
uses the proxy_pass directive to proxy requests beginning with /api/
through to the Node.js API running at http://localhost:4000/
.
Test your new MEAN Stack application running on AWS
Enter the hostname of your AWS EC2 instance in a browser to access and test your new MEAN stack application.
The hostname is the "Public DNS (IPv4)" property located on the instance description tab in the AWS Console.
Need Some Angular Help?
Search fiverr for freelance Angular developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!