Single Page Apps with Vue.js and Flask: Deployment
Deployment to a Virtual Private Server
Welcome to the seventh and final installment to this multi-part tutorial series on full-stack web development using Vue.js and Flask. In this post I will be demonstrating how do deploy the application built throughout this series.
The code for this post can be found on my GitHub account under the branch SeventhPost.
Series Content
- Seup and Getting to Know VueJS
- Navigating Vue Router
- State Management with Vuex
- RESTful API with Flask
- AJAX Integration with REST API
- JWT Authentication
- Deployment to a Virtual Private Server (you are here)
Overview of the Technologies
This tutorial will be covering several technologies necessary to deploy a distributed multi-tier Flask REST API and Vue.js SPA application. Below I have listed the technologies and their uses:
- Ubuntu LTS 16.04: host server for running various applications and servers
- uWSGI: Webserver Gateway Interface (WSGI) container server for executing Python applications (Flask in this case)
- Nginx: Highly performant non-blocking HTTP web server capable of reverse proxying to uWSGI
- Node.js / NPM: Javascript environment for building the Vue.js SPA application
Gettting the Code Ready for Deployment
There are a couple of changes that need to be made to the code to make it more maintainable once the application has been deployed to my production environment.
For example, in api/index.js of the survey-spa
Vue.js application I have hardcoded a variable called API_URL
to point to the dev server http://127.0.0.1:5000/api
. Doing this I will need to remember to change this to the production server’s IP address every time I need to deploy.
Experience has taught me that the there will always be changes to the application requiring future deployments where I am likely to forget to update this IP address. A better approach is to remove the risk of me forgetting to update this and instead utilizing configurations in the build process to handle this for me resulting in less that I have to remember (ie, fewer steps needed) during deployment. This significantly reduces the risk of an unsuccessful deployment on future updates.
I accomplish this by moving over to the survey-spa/config directory and modifying the dev.env.js and prod.env.js files by defining a variable called API_URL
which are assigned a value of http://localhost:5000/api
for dev and http://${process.env.BASE_URL}/api
for prod as shown below:
// dev.env.js
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
API_URL: JSON.stringify(`http://localhost:5000/api`)
})
// prod.env.js
'use strict'
module.exports = {
NODE_ENV: '"production"',
API_URL: JSON.stringify(`http://${process.env.BASE_URL}/api`)
}
Note: the value of process.env.BASE_URL
is an environment variable that I will add to the Ubuntu server’s user .bash_profile and set it equal to the IP address of the server.
Then over in api/index.js I modify the line const API_URL = 'http://127.0.0.1:5000/api'
and set it equal to process.env.API_URL
.
Next, over in the Flask application I need to add a new module called wsgi.py to serve as the entry point to the Flask REST API. The wsgi.py module looks quite similar to the appserver.py module except for that it does not have any calls to the run(...)
method of the app object. This is because the app object will serve as a callable for the uwsgi container server to execute against using its fast binary protocol rather than the regular development server that gets created when app.run(...)
is called.
# backend/wsgi.py
from surveyapi.application import create_app
app = create_app()
With this finished I can push my changes to version control and hop onto my production server to pull down the project and set up the programs I will use to run the application on the production server.
Readying the Ubuntu Server
Next I’ll get onto my production Ubuntu virtual private server which could be hosted by one of the many Cloud services such as AWS, DigitalOcean, Linode, ect… and begin installing all of the goodies I listed in the Overview of the Technologies section.
$ apt-get update
$ apt-get install python3-pip python3-dev python3-venv nginx nodejs npm
With those installs out of the way I can now create a user called “survey” to execute the application under and house the code.
$ adduser survey
$ usermod -aG sudo survey
$ su survey
$ cd
I should now be in the “survey” user’s home directory at /home/survey.
With the survey user created I can update the .bash_profile file to contain the IP address of my production server by adding this line to the end of the file. Note that 123.45.67.89 represents a fake a IP address of my server. Replace it with your true IP address if you are following along.
export BASE_URL=123.45.67.89
Next I want to tell the firewall (ufw) that OpenSSH is acceptable and enable it.
$ sudo ufw allow OpenSSH
$ sudo ufw enable
With this done I will now clone the repo onto the server so I can build and deploy it.
$ git clone https://github.com/amcquistan/flask-vuejs-survey.git
Now I will cd into flask-vuejs-survey/frontend/survey-spa and install the frontend dependencies as well as build the production application.
$ cd flask-vuejs-survey/frontend/survey-spa
$ npm install
$ npm run build
This creates a new directory called “dist”, which will contain an index.html page and a directory called “static” that contains all the compiled CSS and JavaScript files. These are what I will have Nginx server up to constitute the SPA’s front-end application.
Next up I will create a virtual environment in the /home/survey directory for an isolated Python3 interpreter to run the Python application. Once installed I activate it and move into the backend project directory to install its dependency packages specified in the requirements.txt file.
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ cd flask-vuejs-survey/backend
(venv) $ pip install -r requirements.txt
Now I can initialize the sqlite database and run the migrations to create the various database tables required by the REST API.
(venv) $ python manage.py db upgrade
At this point I would like to fire up the Flask dev server to make sure that all is working as expected. Before doing so I need to tell the ufw
service to allow traffic in on port 5000.
(venv) $ sudo ufw allow 5000
(venv) $ python appserver.py
In a browser I can now go to http://123.45.67.89:5000/api/surveys/
and I should see a simple JSON reponse of []
because there are no surveys in this database yet but, this does indicate that a succesful request was made. Additionally, in the terminal connected to the server there should be a logged message for the GET request issued from my browser.
I key in Ctrl+C in the terminal to kill the Flask dev server and move on to configuring uwsgi to control the execution of my Flask REST API. If you are wondering where uwsgi came from it is specified as a requirement in the requirements.txt file that I pip installed with earlier.
Setting up uWSGI Container Server
Similar to what I just did with the Flask dev server I will now test that the uWSGI server can serve up the application as follows.
(venv) $ uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app
Again, going to my browser and refreshing the same request I made previously should return an empty JSON array response. Once satisfied with my progress I can again key Ctrl+C into the terminal and move on.
There are two more steps I would like to do to complete the configuration of the uWSGI container server. One step is to create a configuration file that uWSGI will read in which will replace many of those command line flags and arguments I used above. The second step is to create a systemd service file to manage the uWSGI container server as a service like many of the others already running on the Ubuntu server.
In the backend directory I make a file called surveyapi.ini and fill it with the following:
[uwsgi]
module = wsgi:app
master = true
processes = 4
socket = myproject.sock
chmod-socket = 660
vacuum = true
die-on-term = true
This config file lets uWSGI know that the callable is the app object inside of the wsgi.py module. It also tells it to spawn and use four processes to handle application requests communicated over a socket file called surveyapi.sock which has a loose enough permission to allow the Nginx web server to read and write from it. The vacuum
and die-on-term
settings are to ensure proper cleanup.
For the systemd service file I need to create a file called surveyapi.service
in the /etc/systemd/system directory and add some descriptors plus access, write, and execution commands like so:
(venv) $ sudo nano /etc/systemd/system/surveyapi.service
Then populate it with the following:
[Unit]
Description=uWSGI Python container server
After=network.target
[Service]
User=survey
Group=www-data
WorkingDirectory=/home/survey/flask-vuejs-survey/backend
Environment="PATH=/home/survey/venv/bin"
ExecStart=/home/survey/venv/bin/uwsgi --ini surveyapi.ini
[Install]
WantedBy=multi-user.target
Now I can start the service and check its status and make sure the backend directory now contains surveyapi.sock.
(venv) $ sudo systemctl start surveyapi
(venv) $ sudo systemctl status surveyapi
Loaded: loaded (/etc/systemd/system/surveyapi.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2018-04-23 19:23:01 UTC; 2min 28s ago
Main PID: 11221 (uwsgi)
Tasks: 6
Memory: 28.1M
CPU: 384ms
CGroup: /system.slice/surveyapi.service
├─11221 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
├─11226 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
├─11227 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
├─11228 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
├─11229 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
└─11230 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: mapped 437520 bytes (427 KB) for 5 cores
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: *** Operational MODE: preforking ***
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x8b4c30 pid: 112
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: *** uWSGI is running in multiple interpreter mode ***
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI master process (pid: 11221)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 1 (pid: 11226, cores: 1)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 2 (pid: 11227, cores: 1)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 3 (pid: 11228, cores: 1)
lines 1-23
(venv) $ ls -l /home/survey/flask-vuejs-survey/backend
-rw-rw-r-- 1 survey survey 201 Apr 23 18:18 appserver.py
-rw-rw-r-- 1 survey survey 745 Apr 23 17:55 manage.py
drwxrwxr-x 4 survey survey 4096 Apr 23 18:06 migrations
drwxrwxr-x 2 survey survey 4096 Apr 23 18:52 __pycache__
-rw-rw-r-- 1 survey survey 397 Apr 23 18:46 requirements.txt
drwxrwxr-x 3 survey survey 4096 Apr 23 18:06 surveyapi
-rw-rw-r-- 1 survey survey 133 Apr 23 19:04 surveyapi.ini
srw-rw---- 1 survey www-data 0 Apr 23 19:23 surveyapi.sock
-rw-r--r-- 1 survey survey 10240 Apr 23 18:19 survey.db
-rw-rw-r-- 1 survey survey 84 Apr 23 18:42 wsgi.py
Excellent! The last thing I should do is enable automatic starting each time the system boots up ensuring that the application is always up.
(venv) $ sudo systemctl enable surveyapi
Setting Up Nginx
I will utilize Nginx to serve static content such as HTML, CSS, and JavaScript as well as to reverse proxy REST API calls to the Flask / uWSGI application. To set up nginx to accomplish these things I will need to create a config file which defines how to manage these various requests.
Over in /etc/nginx/sites-available I will create a file called survey which will contain the following:
server {
listen 80;
server_name 123.45.67.89;
location /api {
include uwsgi_params;
uwsgi_pass unix:/home/survey/flask-vuejs-survey/backend/surveyapi.sock;
}
location / {
root /home/survey/flask-vuejs-survey/frontend/survey-spa/dist;
try_files $uri $uri/ /index.html;
}
}
This file creates a new server block configuration which says to listen to IP address 123.45.67.89 on the standard HTTP port of 80. Then it says look for any URI paths beginning with /api and reverse proxy that to the Flask / uWSGI REST API server using the previously defined socket file. Lastly, the config says to catch everything else under / and serve up the index.html file in the dist directory created when I built the Vue.js front-end SPA application prior.
With this config file created I need to let Nginx know that it is an available site by creating a symbolic link to the /etc/nginx/sites-enabled directory like so:
$ sudo ln -s /etc/nginx/sites-available/survey /etc/nginx/sites-enabled
To allow traffic over the HTTP port and bind to Nginx I will issue the following update to ufw
as well as close the previously opened 5000 port.
$ sudo ufw delete allow 5000
$ sudo ufw allow 'Nginx Full'
Following this command I will need to restart the Nginx service like so for the updates to take effect.
$ sudo systemctl restart nginx
Now I can go to my browser again and visit http://123.454.67.89
and I am presented with the survey application I’ve shown in prior articles.
Conclusion
Well this is the concluding post to this multi-part tutorial series on how to utilize Flask and Vue.js to build a REST API enabled SPA application. I have attempted to cover most of the important topics that are common to many web application use cases assuming very little prior knowledge of the Flask and Vue.js technologies used.
I thank you for following along with this series and please do not be shy about commenting or critiquing below.