Docker + Flask + Vue + Nginx - deployment and development in one package (2)
Published: 2020-04-11 17:34:27

Last time I set up a flask environment with Nginx and it allows hot reload etc. So practically we can test our code in development and directly deploy it without any changes. (other than the Nginx file for a little bit).


Now we want to incorporate a front end, and I prefer `Vue.js` for its simplicity. As it turns out, we don't have to install anything locally on our host machine. Everything can be done in the container.

The idea is to run `vue ui` on our container, which will allow us to serve and build our web app later. And all we need to do is to ports our 8000 (vue ui) and 8080 (app). 

# Chapter 4: Vue.js frontend

* Create a `frontend` folder

* Create the `Dockerfile`
```
FROM node:lts-alpine

# install project dependencies
RUN npm install -g @vue/cli

# copy project files and folders to the current working directory (i.e. 'app' folder)
ADD . /frontend


# vue ui --headless --port 8000 --host 0.0.0.0
CMD [ "vue", "ui", "--headless" , "--port", "8000",  "--host", "0.0.0.0" ]

```
### NOTE: I spent some time to figure it out that the vue ui has to be hosted on 0.0.0.0:8000, the localhost of container doesn't work. You can read more here: https://pythonspeed.com/articles/docker-connection-refused/

* Then modify the `docker-compose.yml` by adding the following:

```
    frontend:
        build: ./frontend
        container_name:  frontend
        restart: always
        #  mount the volumes so we can change code and hot reload
        volumes:
            - './frontend:/frontend'

        #  port allows the host machine to link to container
        #  8000 for vue ui, 5000 for our vue app
        ports:
            - "8000:8000"
            - "5000:5000"

```

* Now we can create a project using the vue ui in our `frontend` folder. A problem I noticed is that vue seems to have issue overwriting our frontend folder, so instead it is making a `frontend/frontend` nested folder. No big harm anyway.

* Creating the project just using the default setting. Then in vue ui go to `Tasks` --> `serve`, change the parameters to be `host: 0.0.0.0, port: 5000`. And then serve.

* Now go to `localhost:5000`, and your vue app should be up and running. Changing the file on your host machine will reflect the changes in real time, so we are good to develop.

## PS: if you are having problem of modifying the frontend folder, use `sudo chmod -R 777 frontend/`


# Chapter 5: Review the other code and fix 

* Now that we have both our frontend and backend connected, we need to make some modification. 

* In our `nginx.conf`:
```
server {
    listen 80;
    
    # https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04
    #  listen to our npm server.
    location / {
        proxy_pass http://frontend:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /api {
        include uwsgi_params;
        uwsgi_pass backend:8080;
    }
}
```


* Now that we listen to our uWSGI server at localhost:80/api and listen to our npm server at localhost:80/. We need to modify the route in our python `app.py`, simply by changing `/helloworld/` to `/api/helloworld/`. 

`app.py`

```
from flask import Flask
app = Flask(__name__)

@app.route('/api/hello')
def hello_world():
    return 'Still hot reloading!'

if __name__ == "__main__":
    app.run(host='0.0.0.0')

```




# Chapter 6: communication between backend and frontend
* Now, our frontend can communicate with our backend through `localhost/api` or `backend:8080`, but the `backend` connection is only made available between containers. **When we access our frontend app via browser, `backend` will not be accessible!!** Instead, we have to use the first option. 

* Luckily this doesn't mean we need to do anything different between development and deployment. All we need to do is to use the relative path such as `/api/hello` to access to our backend.


* To test our connection, we can do a simple axios call from frontend to backend.

`frontend/components/HelloWorld.vue`
```
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button v-on:click="test_axios">Test access to our backend</button>
  </div>
</template>
```

```

<script>
import axios from 'axios';

export default {
  name: 'HelloWorld',
  data() {
    return {
      
    }
  },
  props: {
    msg: String
  },
  mounted() {
    this.test_axios()
  },
methods: {
    test_axios: function () {
      const baseURI = '/api/hello'
      // const baseURI = 'http://localhost:80/api/hello'
      axios.get(baseURI)
      .then((result) => {
        console.log(result)
      }).catch(function (error) {
           console.log(error);
        });
    }
  },
}
</script>

```


# Chapter 7: a simple user case for get, post requests.

* So above we are still using a simple hello_world and it may not be a good enough starting point for anyone who wants a skeleton project to work with.

* Let's modify our frontend a bit to make it more interesting.

`frontend/HelloWorld.vue`
```
<template>
  <div class="hello">
    <button v-on:click="test_get">Fetch info from our backend</button>
    <br />
    <input v-model="user_name" placeholder="user_name" />
    <br />
    <button v-on:click="test_post">Send your name to backend</button>
    <div>{{backend_response}}</div>
  </div>
</template>
```

```
<script>
import axios from "axios";

export default {
  name: "HelloWorld",
  data() {
    return {
      user_name: "",
      backend_response: ""
    };
  },
  props: {
    msg: String
  },
  mounted() {},
  methods: {
    test_get: function() {
      var baseURI = "/api/test_get";
      var vm = this;
      // const baseURI = 'http://localhost:80/api/hello'
      axios
        .get(baseURI)
        .then(response => {
          console.log(response);
          vm.backend_response = response.data.message;
        })
        .catch(function(error) {
          console.log(error);
        });
    },
    test_post: function() {
      var baseURI = "/api/test_post";
      var vm = this;
      // const baseURI = 'http://localhost:80/api/hello'
      axios
        .post(baseURI, {
          user_name: vm.user_name
        })
        .then(response => {
          console.log(response.data);
          vm.backend_response = response.data;
        })
        .catch(function(error) {
          console.log(error);
        });
    }
  }
};
</script>

```

### Note here I use `var vm = this` for some arrow functions, this is a nice trick that will save you from being insane.. https://gist.github.com/JacobBennett/7b32b4914311c0ac0f28a1fdc411b9a7


* And our python backend is also modified to accept get and post

`backend/app.py`

```
from flask import Flask, jsonify, request
from flask_cors import CORS, cross_origin
import json

app = Flask(__name__)
CORS(app)


@app.route('/api/test_get', methods=['GET'])
@cross_origin()
def test_get():
    return jsonify({'message': 'Hello, I am your backend'})

@app.route('/api/test_post', methods=['POST'])
@cross_origin()
def test_post():
    if request.method == "POST":
        response = request.get_json()
        result = "Hello " + response['user_name'] 
        return  result

if __name__ == "__main__":
    app.run(host='0.0.0.0')
```


* Now you are all good!
* Next chapter we will be connecting to a mongodb and I think we are done!