DEV Community

Michael Bukachi
Michael Bukachi

Posted on • Updated on

Flask Vue.js Integration Tutorial

This tutorial answers the question, "How do I integrate Vue.js with Flask?" Since you are reading this tutorial, I assume that you know that Flask is a Python microframework built for rapid web development. If you are not familiar with flask or probably think that I am going to talk about a thermos 😜, then I suggest reading about it here before proceeding with this tutorial.

Vue.js is a progressive framework for building user interfaces. If you are not familiar with it, you can read about it here.

Now that you are familiar with both Flask and Vue.js, we can begin.

Flask Setup

Let's install a couple of dependencies first:

pip install --user cookiecutter

Cookiecutter is an awesome command-line utility for quickly bootstraping project templates. We are using cookiecutter so that we don't spend too much time setting up the project. Remember, flask is not batteries included like Django, so quite a bit of work has to be put into the initial setup of a project.

Now that you have installed Cookiecutter, we need grab a project template. For this tutorial, we just need a simple flask API. Run the following commands:

cookiecutter gh:mobidevke/cookiecutter-flask-api-starter

You should get the following output:

repo_name [api-starter]: flask-vuejs-tutorial
api_name [Api]: api
version [1.0.0]: 1.0.0

A folder called flask-vuejs-tutorial should be created. Navigate into that folder and you should see the following structure:

├── app
│   ├── config.py
│   ├── factory.py
│   ├── __init__.py
│   ├── models
│   │   ├── base.py
│   │   ├── database.py
│   │   ├── datastore.py
│   │   └── __init__.py
│   ├── resources
│   │   ├── example.py
│   │   └── __init__.py
│   └── utils.py
├── pytest.ini
├── README.md
├── requirements.txt
├── settings.py
├── tests
│   ├── conftest.py
│   ├── __init__.py
│   ├── test_app.py
│   ├── test_models.py
│   ├── test_resources.py
│   └── utils.py
├── unit-tests.sh
└── wsgi.py

Beautiful, isn't it 😃?

Before we proceed we need to setup a virtual environment. Run:

python -m venv venv

You can now open the project folder using your favourite IDE/Text Editor. Remember to activate the virtual environment before proceeding to the next step.
Now we can install our dependencies. Run:

pip install -r requirements.txt

Once done open app/config.py. You'll notice that this API template uses a postgres database connection. You can setup a postgres db with the necessary credentials, if you don't mind. Otherwise, replace the contents of that folder with the following lines of code:

import os


class Config:
    ERROR_404_HELP = False

    SECRET_KEY = os.getenv('APP_SECRET', 'secret key')

    SQLALCHEMY_DATABASE_URI = 'sqlite:///tutorial.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    DOC_USERNAME = 'api'
    DOC_PASSWORD = 'password'


class DevConfig(Config):
    DEBUG = True


class TestConfig(Config):
    SQLALCHEMY_DATABASE_URI = 'sqlite://'
    TESTING = True
    DEBUG = True


class ProdConfig(Config):
    DEBUG = False


config = {
    'development': DevConfig,
    'testing': TestConfig,
    'production': ProdConfig
}

We have removed all postgres configurations in favour of sqlite ones. If you want to use postgres, leave the conf.py file untouched.
We now need to export our flask application. Run:

export FLASK_APP=wsgi:app

Now that we have finished setting up our flask API, run:

flask run

then open http://127.0.0.1:5000/example. You should see the following:

{"message": "Success"}

Vue.js setup

Now that our API is ready, we can proceed to bootstrap the vue application.
The first thing we need to do is install the vue cli. Run:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

Once the installation finishes, you can check that you have the right version (3.x) with this command:

vue --version

At the root of your project folder run:

vue create web

I chose default (babel, eslint) as the preset and yarn as my package manager. If you are familiar with node projects you can go ahead and choose your preferred options. If not, just follow the defaults for this tutorial.
Now navigate into the new clearly created web folder and run:

yarn serve
# OR
npm run serve

If you navigate to http://localhost:8080/, you should see a Welcome to Your Vue.js App text.

Now we are ready to start the integrations.
Woohoo

In the web folder, create a file called vue.config.js and paste the following contents:

const path = require('path');

module.exports = {
  assetsDir: '../static',
  baseUrl: '',
  publicPath: undefined,
  outputDir: path.resolve(__dirname, '../app/templates'),
  runtimeCompiler: undefined,
  productionSourceMap: undefined,
  parallel: undefined,
  css: undefined
};

Note If you are using Vue CLI 3.3 and above use publicPath instead of baseUrl.
Here, are defining some configurations for the vue cli.
We are only interested in three fields: assetsDir, baseUrl, outputDir.
Let's start with the outputDir.
This folder holds the location of the built vue files, that is, the folder that will hold the index.html that wil load the vue app. If you observe the path provided, you'll notice that the folder is inside the app module of the flask application.
The assetsDir holds the folder for the static files (css, js etc). Note It is relative to the value provided in the outputDir field.
Finally, the baseUrl field will hold the path prefix for the static files in the index.html. You can check this to find out more information about other configuration options.
Now run:

yarn build
# OR
npm run build

If you open the app folder, you will notice that two new folders have been created, templates and static. They contain the built vue files.
Now create a views.py file in the app folder and paste the following contents:

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

sample_page = Blueprint('sample_page', 'sample_page', template_folder='templates')


@sample_page.route('/sample')
def get_sample():
    try:
        return render_template('index.html')
    except TemplateNotFound:
        abort(404)

Now, what's going on here?
Well, we are creating a flask blueprint named sample_page and adding a route to it. This route will render our vue app.

Open __init__.py and add the following lines below app = f.flask:

.....
app = f.flask
from .views import sample_page

app.register_blueprint(sample_page, url_prefix='/views')

Here, we are registering the blueprint we created earlier.
We are giving the blueprint a url prefix so that our vue app is accessible from /views/sample.

The moment of truth has arrived.
Moment

Open http://127.0.0.1:5000/views/sample you should see the following:

Image

If you check the logs, you will see the built resources were loaded correctly:

 * Serving Flask app "wsgi:app"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [24/May/2019 20:45:02] "GET /views/sample HTTP/1.1" 200 -
127.0.0.1 - - [24/May/2019 20:45:02] "GET /static/css/app.e2713bb0.css HTTP/1.1" 200 -
127.0.0.1 - - [24/May/2019 20:45:02] "GET /static/js/chunk-vendors.b10d6c99.js HTTP/1.1" 200 -
127.0.0.1 - - [24/May/2019 20:45:02] "GET /static/js/app.c249faaa.js HTTP/1.1" 200 -
127.0.0.1 - - [24/May/2019 20:45:02] "GET /static/img/logo.82b9c7a5.png HTTP/1.1" 200 -
127.0.0.1 - - [24/May/2019 20:45:02] "GET /views/favicon.ico HTTP/1.1" 404 -

You have successfully integrated Flask with Vuejs 😄.

The source code for this tutorial can be found here.

Top comments (24)

Collapse
 
cynthiakamau profile image
CynthiaKamau

This is my application structure, and every time i try to access the routes with blueprints i get this error:

return self.view_functionsrule.endpoint
TypeError: get() missing 1 required positional argument: 'self'

The routes are in this format :
api.add_resource(TrayResource, '/tray', '/trays')
api.add_resource(RackResource, '/rack', '/racks')

Should i add the blueprints and register them in the init file ad well?

Collapse
 
michaelbukachi profile image
Michael Bukachi

Hello,
Do you mind sharing the code in TrayResource or RackResource?

Collapse
 
cynthiakamau profile image
CynthiaKamau

from flask import current_app, request, Blueprint, render_template
from flask_jwt_extended import jwt_required
from flask_restful import Api, fields, marshal, reqparse

from api.models.database import BaseModel
from api.models.tray import Tray
from api.resources.base_resource import BaseResource
from api.utils import format_and_lower_str, log_create, log_duplicate, log_update, log_delete, non_empty_string, \
has_required_request_params

tray_bp = Blueprint('tray_bp', 'tray_page', template_folder='templates')

class TrayResource(BaseResource):
fields = {
'number': fields.Integer,
'rack.number': fields.Integer,
'code': fields.String
}

@tray_bp.route('/tray')
def get(self):
    if request.headers.get('code') is not None:
        code = format_and_lower_str(request.headers['code'])()
        tray = TrayResource.get_tray(code)
        data = marshal(tray, self.fields)
        return render_template('index.html')
        #return BaseResource.send_json_message(data, 200)
    else:
        trays = Tray.query.all()
        data = marshal(trays, self.fields)
        return BaseResource.send_json_message(data, 200)

@jwt_required
def post(self):
    args = TrayResource.tray_parser()
    rack = int(args['rack'])
    number = int(args['number'])
    code = format_and_lower_str(args['code'])()

    if not Tray.tray_exists(code):
        try:
            tray = Tray(rack_id=rack, number=number, code=code)
            BaseModel.db.session.add(tray)
            BaseModel.db.session.commit()
            log_create(tray)
            return BaseResource.send_json_message("Added new tray", 201)

        except Exception as e:
            current_app.logger.error(e)
            BaseModel.db.session.rollback()
            return BaseResource.send_json_message("Error while adding tray", 500)
    log_duplicate(Tray.query.filter(Tray.code == code).first())
    return BaseResource.send_json_message("Tray already exists", 500)

@jwt_required
@has_required_request_params
def put(self):
    code = format_and_lower_str(request.headers['code'])()
    tray = TrayResource.get_tray(code)
    if tray is not None:
        args = TrayResource.tray_parser()
        rack = int(args['rack'])
        number = int(args['number'])
        code = format_and_lower_str(args['code'])()

        if rack != tray.rack_id or number != tray.number or code != tray.code:
            try:
                tray.rack_id = rack
                tray.number = number
                tray.code = code
                BaseModel.db.session.commit()
                log_update(tray, tray)  # todo: log old record
                return BaseResource.send_json_message("Updated tray", 202)

            except Exception as e:
                current_app.logger.error(e)
                BaseModel.db.session.rollback()
                return BaseResource.send_json_message("Error while adding tray. Another tray has that number", 500)
        return BaseResource.send_json_message("No changes made", 304)
    return BaseResource.send_json_message("Tray not found", 404)

@jwt_required
@has_required_request_params
def delete(self):
    code = format_and_lower_str(request.headers['code'])()
    tray = TrayResource.get_tray(code)

    if not tray:
        return BaseResource.send_json_message("Tray not found", 404)

    BaseModel.db.session.delete(tray)
    BaseModel.db.session.commit()
    log_delete(tray)
    return BaseResource.send_json_message("Tray deleted", 200)

@staticmethod
def tray_parser():
    parser = reqparse.RequestParser()
    parser.add_argument('rack', required=True)
    parser.add_argument('number', required=True)
    parser.add_argument('code', required=True, type=non_empty_string)

    args = parser.parse_args()
    return args

@staticmethod
def get_tray(code):
    return BaseModel.db.session.query(Tray).filter_by(code=code).first()
Thread Thread
 
michaelbukachi profile image
Michael Bukachi

Are you using Flask-Restful or Flask-Restplus?

Thread Thread
 
cynthiakamau profile image
CynthiaKamau

Flask-restful

Thread Thread
 
michaelbukachi profile image
Michael Bukachi

If you are using Flask-Restful then you don't need to use blueprints. Also, remove this @tray_bp.route('/tray') line from your resource methods. For further questions, I'd suggest asking your question here or posting the question on stackoverflow.

Thread Thread
 
cynthiakamau profile image
CynthiaKamau

Thanks

Collapse
 
ryuzakyl profile image
L

Nice work!

I was attempting to deploy the Flask API (no Vue.js for now) based on the Flask part of this tutorial, but I'm having some issues. I'm rather new with Flask :(.

I activate my virtualenv, but when running 'flask run' I keep getting the same error:

Error: While importing "wsgi", an ImportError was raised:

Traceback (most recent call last):
File "/home/ubuntu/.virtualenvs/scraphat_env/lib/python3.6/site-packages/flask/cli.py", line 235, in locate_app
import(module_name)
File "/home/ubuntu/Documents/scraphat_api/wsgi.py", line 11, in
from app import create_app
File "/home/ubuntu/Documents/scraphat_api/app/init.py", line 4, in
from .factory import Factory
File "/home/ubuntu/Documents/scraphat_api/app/factory.py", line 8, in
from .config import config
ModuleNotFoundError: No module named 'app.config'

I've also tried yo serve the Flask app with gunicorn, and get the same error.

Is there something I'm missing? I'm sure that there must be something I'm not doing right.

Any help would be appreciated.

Collapse
 
michaelbukachi profile image
Michael Bukachi

Hello. Thanks for the feedback :)
Can you confirm and check if the file config.py is present?

Collapse
 
ryuzakyl profile image
L

Thank you very much for the quick response.

The file is indeed present. Next, I show my project directory structure.

.
├── app
│ ├── config.py
│ ├── factory.py
│ ├── init.py
│ ├── models
│ │ ├── base.py
│ │ ├── database.py
│ │ ├── datastore.py
│ │ └── init.py
│ ├── resources
│ │ ├── health.py
│ │ ├── init.py
│ │ └── measures.py
│ └── utils.py
├── pytest.ini
├── README.md
├── requirements.txt
├── settings.py
├── tests
│ ├── conftest.py
│ ├── init.py
│ ├── test_app.py
│ ├── test_models.py
│ ├── test_resources.py
│ └── utils.py
├── unit-tests.sh
└── wsgi.py

Thanks for all the help.

Thread Thread
 
michaelbukachi profile image
Michael Bukachi

What do you get when you run python wsgi.py ?

Thread Thread
 
ryuzakyl profile image
L

If I do:

. workon project_env
. cd /path/to/project/root
. python wsgi.py

I get the following:

Traceback (most recent call last):
File "wsgi.py", line 11, in
from app import create_app
File "/home/ubuntu/Documents/scraphat_api/app/init.py", line 4, in
from .factory import Factory
File "/home/ubuntu/Documents/scraphat_api/app/factory.py", line 8, in
from .config import config
ModuleNotFoundError: No module named 'app.config'

Thank you for the feedback

Collapse
 
mattiasjohnson profile image
mattiasJohnson

If I understood correctly this tutorial shows how you use flask and python when the whole index.html is a vue element. I would like to integrate flask and vue and use vue only for a few divs on my page!

I have been able to get some vue functionality by using <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> and defining some vue stuff directly in my script tags in the same html file. I would however like to be able to use Single File Components .vue files which doesn't work for my approach. I imagine I need to use vue create vueproject in my main directory but after that I'm clueless.

Do you have any ideas how to be able to implement vue with full functionality with flask and only embed the vue components in a few divs on my page?

Collapse
 
michaelbukachi profile image
Michael Bukachi • Edited

It's the same approach. Here's an example of snippets from index.html and main.js

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>web</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app1"></div>
    <div id="app2"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

main.js:

import Vue from 'vue'
import App1 from './App1.vue'
import App2 from './App2.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App1),
}).$mount('#app1')


new Vue({
  render: h => h(App2),
}).$mount('#app2')

Remember to build your vue files otherwise it won't work.

Collapse
 
ipv6_python profile image
Gregory Wendel • Edited

Michael,
Thanks for the great tutorial. I followed your instructions and here are a few notes.
When using the versions below:
Centos 7
node --version
v12.14.1
npm -version
6.13.4
Below are some things I did to get this working
1 - Install postgres and postgres dev before running pip install -r requirements.txt
sudo yum install postgresql-server postgresql-contrib
2 - near the end of your instructions in vue.config.js I removed the line, " baseUrl: '', "
I had the following error before removing the line:
ERROR Invalid options in vue.config.js: "baseUrl" is not allowed

Thanks,
Greg

Collapse
 
michaelbukachi profile image
Michael Bukachi

Hello, thanks for the info. Let me update the tutorial.

Collapse
 
bkairu5 profile image
Halafu

Kazi poa owefu! Good stuff!

Collapse
 
annndrey profile image
Andrey Gonchar

Nice work!
And what is the advantage of this method? Right now Flask is just serving Vue files.
What is the difference between serving Vue with Flask and with npm?

Collapse
 
michaelbukachi profile image
Michael Bukachi

Flask can be used to create APIs which can be consumed by the Vue app. In a situation where you are not using flask, you would have to implement the API in a different framework, say ExpressJs and do integrations from there.

Collapse
 
annndrey profile image
Andrey Gonchar

Thanks for the answer, but still not clear the advantages of this way.
Say you have a Flask with API and a Vue frontend. In your case if Flask fails for some reason, there wouldn’t be a way to inform the user that something is wrong, because Vue is served by Flask too and the end user wouldn’t see the client app.
I suggest to divide both front and backend on the server level. So if Flask fails, you’ll still have the ability to inform the end user that the client app has lost connection with the server.

Thread Thread
 
michaelbukachi profile image
Michael Bukachi

This method is not meant to replace the conventional two-tier design where you have a web app running on it on server and an API on it's own server. I actually always use the two tier approach for major projects. It's easier to manage and maintain. I only use this method for small and hobby projects. Think of vue as a replacement for bootstrap and jquery in a web application.

Collapse
 
evgeniifedulov profile image
Evgenii Fedulov

It's not integration. It's joke.

Collapse
 
hyungjunk profile image
hyungjunk

Great Tutorial. I also want to know about flask integrated with vue and webpack that provides live reloading feature when change made on template files.(js, html or vue)

Collapse
 
michaelbukachi profile image
Michael Bukachi

Thanks!
To use live reloading just run yarn serve or npm run serve. Every change will trigger a live reload. However, you will have to run yarn build or npm run build to integrate vue files with the flask ap[.