Go Back

Deploy a SPA with Ubuntu, Apache, and Nodejs

Posted by Charlie Ontiveros on 2018-06-14

So you've created your full-stack Nodejs application and are now ready to deploy to the internets? After reviewing your options, you've chosen the more traditional route and gone with old school Apache and Ubuntu, but aren't quite sure how to get this working. Well, you've come to the right place.

This tutorial will go over setting up Apache on Ubuntu to serve up a front end website and launch a separate back end Nodejs app to handle requests from the front end.

Pre-requisites

Before starting the tutorial, be sure you have the following:

Node App

In this section, we will set up a very simple Node application that has a separate front and back end.

Front End

  1. Create a folder inside you main project folder to store front end files. I simply called this folder FrontEnd.

  2. Create a file called index.html and populate it with the following information:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Front End</title>
    </head>
    
    <body>
        <h1>This is the front end</h1>
        <form id="form123" method="POST" action="/backend">
            <input type="submit" value="Click me to send POST to back end"></input>
        </form>
    
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
        <script src="frontend.js"></script>
    </body>
    
    </html>

    This is a very simple page that contains a button, which when clicked, will send a request to the back end.

  3. Create a Javascript filed called frontend.js and populate that with the following information:

    // Add Event Listner to Form
    document.getElementById("form123").addEventListener("click", interceptServerRequest)
    
    axios.defaults.baseURL = 'http://localhost:3000'
    
    // Prevent Default Action
    function interceptServerRequest(event) {
        event.preventDefault();
        axios.post('/backend')
            // Instead grab data from response and show it on front end
            .then(res => alert(res.data))
            .catch(err => {
                console.log(err)
                alert("Something went wrong. Check console log.")
            })
    }

    In a SPA, all of the pages are usually generated by the front end itself instead of having the server serve up pages. The Javascript here is handling the request and response from the server rather than letting the browser handle that directly. I'm using the Axios library since it's very easy to use. You should check it out if you haven't already.

Back End

  1. Create another folder under the main project folder to store back end files. I called mine BackEnd to keep it simple and clear.

  2. Navigate to the BackEnd folder and install express using the command below

    npm install --s express
  3. Create a file called "server.js" and populate it with the following information:

    const express= require('express')
    const server = express()
    
    server.post('/backend', (req, res) => {
        res.setHeader('Access-Control-Allow-Origin', '\*');
        console.log("Request received")
        res.send('Data from back end.')
    })
    
    server.listen(3000)

    This will set up a very simple back end with one route, which will send back data to display on the front end. It also includes a change to the response header to allow cross site domain access when testing this locally.

Quick Test

To confirm everything is working correctly, simply start the node application and then open up the index.html page and then click on the button. You should see an alert appear stating "Data received from back end."

Apache Configuration

Now that we've confirmed this is working locally, it's time to move the files to an actual server running Ubuntu and Apache. You can do this however you want. Personally, I use WinSCP which allows me SFTP and drag and drop files in a UI.

  1. Create a folder for this project.

    sudo mkdir /var/www/proxyproject
  2. Move the BackEnd and FrontEnd folders under the proxyproject folder.

Website Settings

If your website is already configured in Apache, you can skip this section.

First things first, we need to create a virtual host file for this tutorial. You can simply think of this as setting up a website in Apache.

  1. Log into your Ubuntu server and navigate to the following folder:

    cd /etc/apache2/sites-available

    The sites-available folder contains the configurations of all the websites Apache can serve up to visitors. A config file will be needed for this tutorial.

  2. Create a copy of the default conf (configuration) file with the following command:

    sudo cp 000-default.conf proxyproject.conf
  3. Open up proxyproject.conf with:

    sudo nano proxyproject.conf
  4. Modify the config file as shown below.

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/proxyproject/FrontEnd
    
    ServerName yourdomainname.com        # Add this line
    ServerAlias www.yourdomainname.com   # Add this line

    If you do not have a domain name yet, comment out the ServerName by adding a # tag in front of it and change the ServerAlias to your server's IP address.

    The DocumentRoot is the path where all website files will be stored. In this case, we only need Apache to serve the front end files. Therefore, we can list FrontEnd as the directory instead of using proxyproject, which contains the back end files.

  5. Activate the config file.

    sudo a2ensite proxyproject.conf
  6. Restart Apache server to make the new configuration take effect.

    service apache2 reload

Enable Apache Modules

In order to run a back end and front end independently, a proxy will need to be configured so Apache will serve up the files on the FrontEnd folder, but forward all other requests to the Node app on the back end. Before configuring the proxy, the following modules will need to be enabled:

  • proxy_http

  • proxy

  • To enable these modules, run the following commands:

    sudo a2enmod proxy
    sudo a2enmod proxy_http
  • Restart the Apache service to make sure they get loaded.

    service apache2 restart

You can check these have been enabled by running the following command, which will list all active Apache2 modules.

apachectl -M

Virtual Hosts and Directives

Among other things, a virtual host file stores a list of instructions for Apache called directives. Apache has over a hundred directives and we will be using the following to set up the proxy:

  • ProxyPass: Sets up a 'forwarding' rule for Apache.
  • ProxyPassMatch: This is the same as ProxyPass except it allows use of regular expressions (regex) for more powerful forwarding rules.
  • ProxyPassReserve: Tells Apache to modify the response headers sent from the proxied location to preserve the original path from the request.

One important thing to note is that the first proxy rule that matches will be applied to the request. Therefore, the list of rules should go from most restrictive to least restrictive.

For security, we will also be overriding the Directory Indexes directives well.

ProxyPassMatch

First, we need to define the pages and resources that we don't want Apache to proxy. This can be done by listing a resource and then following it up with an exclamation mark. In other words, we're creating a whitelist.

  1. Add the following line after the ServerAlias and ServerName in the conf file:

    ProxyPassMatch ^/$ !

    This line tells Apache not to forward requests whose path is only "/".

ProxyPass

Next, we need to whitelist the other resources.

  1. Add the following lines under the ProxyPassMatch

    ProxyPass /index.html !
    ProxyPass /frontend.js !

    Here, we are telling Apache not to forward requests for "/index.html" and "/frontend.js". Although this simple project does not contain a CSS or img folder, these can be added very easily. See below for an example.

    ProxyPass /css !
    ProxyPass /img !
  2. Once the whitelist is complete, we need to set up the actual proxy. Add the following line to the conf file:

    ProxyPass / http://127.0.0.1:3000/

    This will catch all requests that do not match any of rules above and send them to the localhost, which if you recall, will be running the Node app on port 3000.

ProxyPassReverse

  1. Last, but not least, we need to add the ProxyPassReverse directive to make sure server responses match the origin of requests from the front end.

    ProxyPassReverse / http://127.0.0.1:3000/

Directory Configuration

The following is optional, but highly recommended to improve the security of the site.

In order to prevent users from checking out the files on the back end, which can be dangerous, the back end files will need to be hidden. This can be done by turning off the directory indexes option.

  1. Go to the main Apache conf fie.

    cd /etc/apache2/apache2.conf
  2. Find the Directory section and then remove the keyword "Indexes".

    <Directory /var/www/>
            Options Indexes FollowSymLinks
            AllowOverride None
            Require all granted
    </Directory>
    
    Should look like this:
    
    <Directory /var/www/>
            Options FollowSymLinks
            AllowOverride None
            Require all granted
    </Directory>
  3. Save the file and then reload Apache.

    service apache2 reload

Quick Test

  1. Navigate to the BackEnd folder and start the node application.

    nodejs server.js
  2. Launch a web browser and go to your website or type in your server's IP address. This should load the index.html webpage that was created earlier.
  3. Click on the button and you should see a pop-up indicated a response was received from the server.

Conclusion

In this tutorial, we went over how to setup Apache on Ubuntu for a single page application. It also included a step to prevent users from viewing files using Apache Directory feature.