Raphael Ugwu Writer, Software Engineer and a lifelong student.

Setting up an online store with Flask and Vue

12 min read 3602

In this tutorial, we’ll learn how to build an e-commerce store with Vue and Flask (a micro web framework written in Python). Our store will be able to create, read, update, and delete content via a server-side RESTful API powered by Flask.

App requirements

If you have basic knowledge of Vue and Python and want to learn how to build cool stuff across a range of frameworks then this is a good place to get started. Our application will rely on the following frameworks and libraries as major dependencies:

  • Vue: A progressive framework used for building user interfaces
  • Vue CLI: Vue’s command line interface which provides a robust system for rapid Vue JS development
  • Node: JavaScript’s runtime environment that executes JavaScript code outside the browser
  • npm: The default package manager for JavaScript’s runtime environment, NodeJS
  • Flask: A micro web framework written in Python
  • Python: A general-purpose programming language used to develop a wide range of applications
  • virtualenv: A module that creates virtual environments in Python and its related frameworks

The first thing you should check for is if you have Python and virutalenv installed on your machine. If not, you can learn more about these here:

Setting up Flask

Before installing Flask, we’ll first create our project directory via our terminal:

$ mkdir vue-flask-store
$ cd vue-flask-store

In vue-flask-store, create a new directory, name it server. Then with the virtualenv’s venv command, create a virtual environment in vue-flask-store :

$ python3.7 -m venv env
$ source env/bin/activate

Now, install Flask along with the Flask-CORS extension which will assist us in handling cross-origin requests:

(env)$ pip install Flask Flask-Cors

When done with the installation, you should have a folder structure similar to this:

├── VUE-FLASK-STORE
└── env
    ├── bin
    ├── include
    ├── lib / python 3.7 / site-packages
    ├── pyvenv.cfg
└── server    (env)$ pip install Flask Flask-Cors

Setting up Vue

We’ll create a Vue project by using Vue CLI. Navigate to your terminal and install it globally:

$ npm install -g @vue/cli

Then, still in the terminal, navigate to the vue-flask-store project and initialize a new Vue project which we’ll call frontend. When prompted to choose from an array of options, follow the format below:

Vue CLI v3.7.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Linter
? Use history mode for router? Yes
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) No

When we’re done, we’ll have a generated Vue project with a folder structure similar to this:

We made a custom demo for .
No really. Click here to check it out.

├── VUE-FLASK-STORE
└── frontend
    └── public
       ├── index.html
    └── src
       └── assets
          ├── logo.png
       └── components
          ├── HelloWorld.vue
       └── router
          ├── index.js
       ├── App.vue
       ├── main.js

    ├── .editorconfig
    ├── .gitignore
    ├── .babel.config.js
    ├── package-lock.json
    ├── package.json
└── server

In your terminal, navigate to frontend and start the development server:

$ cd client
$ npm run serve

Then on your browser, navigate to http://localhost:8080. You should see a page similar to this:

To connect the requests we’ll be making on the frontend (our Vue app), to the backend (our Flask app), we’ll need to include the axios library in our project. Still in your terminal, install axios in the frontend folder:

$ npm install axios --save

Lastly for some styling, we’ll include bootstrap-vue (an implementation of Bootstrap for VueJS). We’ll install bootstrap-vue just the same way axios was installed:

$ npm install bootstrap-vue --save

Then enable it by importing it in frontend/src/main.js:

// frontend/src/main.js

import BootstrapVue from "bootstrap-vue";
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
Vue.use(BootstrapVue);
Vue.config.productionTip = false;
new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

At this point we have all the dependencies we need, we can then go on to build all the routes that will make up our app.

Building the application

When we’re done building our app it should look similar to this:

We want to build an e-commerce store that has JavaScript courses. A user should be able to perform the following operations:

  • Select if they want the paperback version of a course or not
  • Delete a course from the list of courses
  • Add a course they want to purchase but is not on the default list (which we’ll create in our Flask server)

Creating a server in Flask

The first step would be to create a server in our Flask app. This would contain the default list of courses on our app as well as handles for any routes on the app that our users may want to take. Navigate to the server folder in your Flask app and create a file named app.py , the first thing we’ll do in app.py is to add the default list of courses:

// server/app.py

COURSES = [
    {
        'title': 'Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript ',
        'author': 'David Herman',
        'paperback': True
    },
    {
        'title': 'JavaScript: The Good Parts',
        'author': 'Douglas Crockford',
        'paperback': False    
    },
    {
        'title': 'Eloquent JavaScript: A Modern Introduction to Programming',
        'author': 'Marijn Haverbeke',
        'paperback': True
    }
]    

Then we’ll add a route handler that routes back to this list:

from flask import Flask, jsonify

@app.route('/courses', methods=['GET'])
def all_courses():
    return jsonify({
        'status': 'success',
        'courses': COURSES
    })

Head on to your terminal and run the Flask app (in your virtual environment) by inputting the command below:

(env) server % python app.py

On your browser, test out the route at http://localhost:5000/courses. You should see a page similar to this:

localhost courses

Updating the Vue app

Having created a server, the next step is to update our frontend with its content. In the src/components folder of your Vue app, create a file named Courses.vue . Here, we’ll create a template styled by classes from bootstrap-vue. We’ll use Vue directives as well to handle some logic:

<!-- src/components/Courses.vue -->

    <template>
      <div class="container">
          <h1>Courses</h1>
            <hr>
            <button type="button" class="btn btn-success btn-sm">Add Course</button>
            <table class="table table-hover">
                <tr>
                  <th scope="col">Title</th>
                  <th scope="col">Author</th>
                  <th scope="col">Paperback</th>
                </tr>
              <tbody>
                <tr v-for="(course, index) in courses" :key="index">
                  <td>{{ course.title }}</td>
                  <td>{{ course.author }}</td>
                  <td>
                    <span v-if="course.paperback">Yes</span>
                    <span v-else>No</span>
                  </td>
                  <td>
                  <button type="button" class="btn btn-info btn-sm">Update</button>
                  <button type="button" class="btn btn-danger btn-sm">Delete</button>
                  </td>
                </tr>
              </tbody>
            </table>
      </div>
    </template>

In the template, v-for is used to iterate through the list of courses using the index value as a key. Then v-if is used to render Yes or No options to indicate if a user wants the course in paperback or not.

In the script section of our component, we’ll create a method called getCourses() where we’ll use axios to make a GET request to our server. We’ll then use Vue’s created lifecycle hook to fetch our courses from the server we built with Flask:

<!-- src/components/Courses.vue -->

 <script>
 import axios from 'axios';
 export default {
  data() {
    return {
      courses: [],
    };
  },
  methods: {
    getCourses() {
      const path = 'http://localhost:5000/courses';
      axios.get(path)
        .then((res) => {
          this.courses = res.data.courses;
        })
        .catch((error) => {
          console.error(error);
        });
    },
  },
  created() {
    this.getCourses();
  },
 };
 </script>

There, save Course.vue and in your terminal, run npm run serve. You should have an interface that is closely similar to this:

course vue

Notice we explained everything except the Add Course button. We’ll handle that in the next section where we’ll learn how to modify our server and frontend to handle requests to add new courses from our users.

Modifying the server for POST requests

For a user to add a course to the already existing list of courses, a POST request has to be sent to the server. Let’s modify server/app.py to accept these requests. In app.py, update the existing route handler all_courses to handle POST requests for adding a new course:

from flask import Flask, jsonify, request

@app.route('/courses', methods=['GET', 'POST'])
def all_courses():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        COURSES.append({
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'paperback': post_data.get('paperback')
        })
        response_object['message'] = 'Course added!'
    else:
        response_object['courses'] = COURSES
    return jsonify(response_object)

Next, in the Courses component we’ll add a modal to our frontend to enable users to add courses to the default list:

<!-- src/components/Courses.vue -->

<template>
<b-modal ref="addCourseModal"
         id="course-modal"
         title="Add a new course"
         hide-footer>
  <b-form @submit="onSubmit" @reset="onReset" class="w-100">
  <b-form-group id="form-title-group"
                label="Title:"
                label-for="form-title-input">
      <b-form-input id="form-title-input"
                    type="text"
                    v-model="addCourseForm.title"
                    required
                    placeholder="Enter title">
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-author-group"
                  label="Author:"
                  label-for="form-author-input">
        <b-form-input id="form-author-input"
                      type="text"
                      v-model="addCourseForm.author"
                      required
                      placeholder="Enter author">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-read-group">
      <b-form-checkbox-group v-model="addCourseForm.paperback" id="form-checks">
        <b-form-checkbox value="true">Paperback</b-form-checkbox>
      </b-form-checkbox-group>
    </b-form-group>
    <b-button type="submit" variant="primary">Submit</b-button>
    <b-button type="reset" variant="danger">Reset</b-button>
  </b-form>
</b-modal>
</template>

In the code sample above, we created a modal and used the v-model directive to attach any values a user will input back to our app’s state. Let’s update our script section to include the state addCourseForm which is bound to user input via the v-model directive. When a user updates the input, this state gets updated as well:

<!-- src/components/Courses.vue -->

<script>
import axios from 'axios';
export default {
  data() {
    return {
      courses: [],
      addCourseForm: {
        title: '',
        author: '',
        paperback: [],
      },
    };
  },
  created() {
    this.getCourses();
  },
};
</script>

Then we’ll create different methods to handle each action a user performs. Below is a couple of methods we’ll be creating and how they’ll assist in handling each of our actions:

  • addCourse: This method sends a POST request to /courses to include a new course to the existing list of courses
  • initForm: This method appends every detail of the course that’s to be added to addCourseForm
  • onSubmit: This method executes when a user adds a course successfully. First, default browser behavior is prevented via e.preventDefault(), then the modal used to add the form is closed successfully with this.refs.addCourseModal.hide(), the addCourse method is executed and the form is reinitialized and cleared with initForm()
// src/components/Courses.vue

methods: {
   addCourse(payload) {
     const path = "http://localhost:5000/courses";
     axios
       .post(path, payload)
       .then(() => {
         this.getCourses();
       })
       .catch(error => {
         // eslint-disable-next-line
         console.log(error);
         this.getCourses();
       });
   },
   initForm() {
     this.addCourseForm.title = "";
     this.addCourseForm.author = "";
     this.addCourseForm.paperback = [];
   },
   onSubmit(e) {
     e.preventDefault();
     this.$refs.addCourseModal.hide();
     let paperback = false;
     if (this.addCourseForm.paperback[0]) paperback = true;
     const payload = {
       title: this.addCourseForm.title,
       author: this.addCourseForm.author,
       paperback
     };
     this.addCourse(payload);
     this.initForm();
   }
 }

When we’re done, let’s update the Add Course button in our template so that the modal will be displayed when the button is clicked:

<!-- src/components/Courses.vue -->

<template>
  <button type="button" class="btn btn-success btn-sm" v-b-modal.course-modal>
    Add Course
  </button>
</template>

Now, let’s try to add a course and see what happens:

Adding an alert component

It would be nice for our users to have some sort of alert or popup whenever an action occurs like when a course is added, updated, or deleted. To include an alert for our app, let’s begin by creating a new component which we’ll call Alert.vue. In this component, we’ll use b-alert, an inbuilt component of bootstrap-vue, to display the message we want to show when an alert pops up:

<!-- src/components/Alert.vue -->

<template>
  <div>
    <b-alert variant="success" show>{{ message }}</b-alert>
    <br />
  </div>
</template>

<script>
export default {
  props: ["message"]
};
</script>

At this point, you may be wondering what’s going on here. We specified a message prop which can receive updates from the Courses component as that’s where we’ll export and handle the Alert component. In Courses.vue, import the Alert component and add the message prop to the data() object:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  data() {
    return {
      courses: [],
      addCourseForm: {
        title: "",
        author: "",
        paperback: []
      },
       message: "",
    };
  }
};
</script>

Next, in the addCourse method, we’ll update the message with what we need:

<!-- src/components/Courses.vue -->
...
  addCourse(payload) {
    const path = 'http://localhost:5000/courses';
    axios.post(path, payload)
      .then(() => {
        this.getCourses();
        this.message = 'Course added!';
      })
      .catch((error) => {
        console.log(error);
        this.getCourses();
      });
  }
...

In the data() object, we’ll include another property showMessage which will decide if an alert should be displayed or not, based on if a course has been added or not. showMessage will have an initial boolean value of false:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  data() {
    return {
      courses: [],
      addCourseForm: {
        title: "",
        author: "",
        paperback: []
      },
       message: "",
       showMessage: false,
    };
  }
};
</script>

Then in our template, we’ll update the Alert component with a v-if directive which conditionally renders Alert based on either of the two values – true or false:

<!-- src/components/Courses.vue -->

<template>
  <button>
    <alert :message=message v-if="showMessage"></alert>
  </button>
</template>

Lastly, we’ll update the AddCourse method and set showMessage to true whenever a course is successfully added:

<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    addCourse(payload) {
      const path = "http://localhost:5000/courses";
      axios
        .post(path, payload)
        .then(() => {
          this.getCourses();
          this.message = "Course added!";
          this.showMessage = true;
        })
        .catch(error => {
          console.log(error);
          this.getCourses();
        });
    }
  }
};
</script>

Now, let’s see if our alert will be displayed. Save your work, run npm run serve on your terminal and go to your browser:

Setting up unique identifiers

We may have a situation where two courses have exactly the same name. To handle this, we need each update our users make to have a unique identifier. To achieve this, we’ll use uuid, a module in Python’s library that provides unique IDs. Let’s update our server so all our default courses have random IDs:

# server/app.py

from flask import Flask, jsonify, request
import uuid

COURSES = [
   {
       'id': uuid.uuid4().hex,
       'title': 'Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript ',
       'author': 'David Herman',
       'paperback': True
   },
   {
       'id': uuid.uuid4().hex,
       'title': 'JavaScript: The Good Parts',
       'author': 'Douglas Crockford',
       'paperback': False
   },
   {
       'id': uuid.uuid4().hex,
       'title': 'Eloquent JavaScript: A Modern Introduction to Programming',
       'author': 'Marijn Haverbeke',
       'paperback': True
   }
]

Next, we’ll configure all_courses to process the random IDs each time a user adds a new course:

@app.route('/courses', methods=['GET', 'POST'])
def all_courses():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        COURSES.append({
            'id': uuid.uuid4().hex,
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'paperback': post_data.get('paperback'),
                })
        response_object['message'] = 'Course added!'
    else:
        response_object['courses'] = COURSES
    return jsonify(response_object)

Updating existing courses

Suppose our users want to make changes to courses that exist on our app, we can make this possible by creating another modal in Courses.vue to handle this. In Courses.vue, create another modal called editCourseModal just below addCourseModal. We’ll wrap all the properties of the form we want to create under b-form which is a form element provided by bootstrap-vue. The first property we’ll create an input for is the title of the course. Here we can modify the title to what we want:

<b-form-group  id="form-title-edit-group"  label="Title:"
             label-for="form-title-edit-input"
>
    <b-form-input  id="form-title-edit-input"  type="text"
        v-model="editForm.title"
        required
        placeholder="Enter title"
    >
    </b-form-input>
</b-form-group>

Next, we’ll create an input field for where we can update the name of the author:

<b-form-group id="form-author-edit-group"  label="Author:"
  label-for="form-author-edit-input"
>
    <b-form-input  id="form-author-edit-input"  type="text"
        v-model="editForm.author"
        required
        placeholder="Enter author"
    >
    </b-form-input>
</b-form-group>

Then we’ll create a checkbox, where the user can have the option of modifying if the course should be in paperback or not:

<b-form-group id="form-read-edit-group">
    <b-form-checkbox-group v-model="editForm.paperback" id="form-checks">
        <b-form-checkbox value="true">Paperback</b-form-checkbox>
    </b-form-checkbox-group>
</b-form-group>

Then we’ll create two buttons – Update to update the changes we’ve made and Cancel to close the modal if we no longer want to make the changes:

<b-button-group>
    <b-button type="submit" variant="primary">Update</b-button>
    <b-button type="reset" variant="danger">Cancel</b-button>
</b-button-group>

Finally, we’ll wrap all the elements we just created in the b-form element:

<template>
    <b-modal ref="editCourseModal" id="course-update-modal" title="Update">
        <b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100"  

            <!--Place all the elements here-->

        </b-form>
    </b-modal>
</template>

Next, in the script tag of Courses.vue, we’ll update the state of this modal:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  data() {
    return {
      courses: [],
      addCourseForm: {
        title: "",
        author: "",
        paperback: []
      },
      editForm: {
        id: "",
        title: "",
        author: "",
        paperback: []
      },
      message: "",
      showMessage: false
    };
  }
};
</script>

Next, we’ll modify the Update and Cancel buttons to make updates to a course or delete a course off the list. First, for the Update button, we’ll create a method to update the values in editForm:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {

    methods: {
      editCourse(course) {
      this.editForm = course;
    },
  }

};
</script>

Next, we’ll create a method to handle the submitting of the form containing the updated details:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    editCourse(course) {
      this.editForm = course;
    },
    onSubmitUpdate(e) {
      e.preventDefault();
      this.$refs.editCourseModal.hide();
      let paperback = false;
      if (this.editForm.paperback[0]) paperback = true;
      const payload = {
        title: this.editForm.title,
        author: this.editForm.author,
        paperback
      };
      this.updateBook(payload, this.editForm.id);
    }
  }
};
</script>

Then we’ll use axios to make a request to update our server. We’ll also include a message that will display as an alert each time a book is added:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    updateCourse(payload, courseID) {
      const path = `http://localhost:5000/courses/${courseID}`;
      axios
        .put(path, payload)
        .then(() => {
          this.getCourses();
          this.message = 'Course updated!';
          this.showMessage = true;
        })
        .catch(error => {
          console.error(error);
          this.getCourses();
        });
    }
  }
};
</script>

To cancel an update, we’ll create a method that closes the editForm modal, reinitializes the form and makes a request to get the current list of courses:

<!-- src/components/Courses.vue -->

<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    onResetUpdate(evt) {
      evt.preventDefault();
      this.$refs.editBookModal.hide();
      this.initForm();
      this.getBooks();
    }
  }
};
</script>

Then we’ll update initForm to include properties of editForm as well:

<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    initForm() {
      this.addBookForm.title = "";
      this.addBookForm.author = "";
      this.addBookForm.read = [];
      this.editForm.id = "";
      this.editForm.title = "";
      this.editForm.author = "";
      this.editForm.read = [];
    }
  }
};
</script>

There. Now let’s see if our updates and changes work:

Deleting existing courses

To delete an existing course from the list of courses, we’ll create a method in the script section of Courses.vue. Our method should be able to make a delete request via axios to delete a course by its ID, get the current list of courses, and display an alert that says the course has been deleted:

<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
  methods: {
    removeCourse(courseID) {
      const path = `http://localhost:5000/courses/${courseID}`;
      axios
        .delete(path)
        .then(() => {
          this.getCourses();
          this.message = " Course removed!";
          this.showMessage = true;
        })
        .catch(error => {
          console.error(error);
          this.getCourses();
        });
    },
    onDeleteCourse(course) {
      this.removeCourse(course.id);
    }
  }
};
</script>

Let’s review how this works:

Conclusion

Frameworks were built to give developers more leverage and functionality with programming languages. Thankfully we’ve been able to show how awesome this leverage could be if it is used right. Readers are encouraged to go further and improve on this – add a shopping cart, images, or a payment method maybe. You can check out the full code of this project here on GitHub.

 

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Raphael Ugwu Writer, Software Engineer and a lifelong student.

2 Replies to “Setting up an online store with Flask and Vue”

  1. Hello, I am trying to follow this tutorial (beginner using Vue), I am at the point in this tutorial where you create the Courses.vue file in components folder. Once I run the server after this, I get the original Vue GUI on port 8080. I can see that I have a folder called ‘views’ in frontend/src that contains ‘/Home.vue’ and ‘/About.vue’. When I look in index.js in router I can see that these have a depencancy. Should this index.js have updated or have I somehow missed somthing?

    Thank you for any help

Leave a Reply