12 January 2017, a calm Thursday at 21:32 · Vuejs Flask Eve Tutorial

Write your first web app using python and javascript

This tutorial is targeted to beginners who have no experience in vuejs and eve, though a basic experience in python, javascript, html and css is preferred.

After doing this tutorial, you will have an app like this

Here is the source code, if want to dive in before we begin.

In this tutorial I will teach you how to write a web application using Eve which is a framework in python (written on top of flask), Eve will serve us to build our REST API with little to no backend code, we will also use Vuejs which is javascript framework for building user interfaces, then I will teach you how to deploy your web app.

So the app example I picked was a todo app, pretty classic :)

So let's keep it simple, a user should be able to post, and search todos and mark them as done and maybe delete them.

Installing requirements

Installing virtualenv

For this tutorial we will use python 2, ( I wanted to use python3 but webassets does not support python3 sadly), so you want to install python2.

Then we want to make a new separate environment to develop our app, so you want to install virtualenv which is a program to make isolated python environments, to install it do:

$ sudo pip install virtualenv

or without the sudo if you use a windows machine.

then you want to create a virtualenv using:

$ virtualenv venv

this command tells virtualenv to make a new virtualenv in the venv directory.

If you use a linux machine and your /usr/bin/python is python3 use the --python=/usr/bin/python2 prefix

once we are done with that, let's activate it shall we?

$ source venv/bin/activate

you should see (venv) besides your prompt.

Installing python libs requirements

Let's start by downloading Eve from Pypi using

(venv) $ pip install Eve

This will also install Pymongo and Flask and bunch of other stuff, don't worry for now let's just get a hello world running;

Install MongoDB

We will use mongodb as a datastore so you need it too.

Writing the backend

First make make your directory tree like this

├── autoapp.py
├── manage.py
├── settings.py
└── todoapp
    ├── config.py
    ├── extensions.py
    ├── frontend
    │   ├── __init__.py
    │   └── views.py
    ├── __init__.py
    ├── static
    │   ├── css
    │   │   └── app.css
    │   └── js
    │       └── app.js
    └── templates
        ├── base.html
        └── index.html

autoapp.py: will expose your application object to use it later. manage.py: We will use flask-script, to manage our ecosystem. settings.py: this is where the Eve config should go. config.py: our configuration for various scenarios. extensions.py : where we will init our extensions. todoapp/init.py: where we init the app. views.py: where we will serve the static files, though you can do it with a web server. app.css: where all the css goes. app.js: where all the js goes. base.html and index.html are just template files.

You could do this in one file since flask is very flexible and does not impose anything on you but I really advice you to use something like this in your next projects.

So let's start, fire up you favorite text editor or IDE (Emacs ahem).

First of all let's just make a working api, where one could publish, get, delete, mark done or undone his or her todos.

here is a the eve's settings.py

MONGO_QUERY_BLACKLIST = ['$where'] # in default the blacklisted mongodb querys are where and regex
MONGO_HOST = 'localhost'
MONGO_PORT = 27017 # use your port
MONGO_DBNAME = 'todoapp_database' # the mongo db name
URL_PREFIX = 'api'
API_VERSION = 'v1' # always a good practice to version your api

# the todos collection should have two documents
# a todotext with a minimum length of 4, and done document which will
# tell if a task is done or not
schema = {
    'todotext': {
        'type': 'string',
        'minlength': 4
    },
    'done': {
        'type': 'boolean',
        'default': False
    }
}

DOMAIN = {
    'todos': {
        'item_title': 'todo',
        'resource_methods': ['GET', 'POST'], # a resource references a mongodb collection
        # so we should only be able to add to or to get from a todo list
        'schema': schema, # the used schema
        'item_methods': ['GET', 'PATCH', 'DELETE'] # on a single todo we should be able
        # to get it, patch it (edit it (to mark for done and undone)) and delete it.
    }
}

So let's start actually make the app:

in the todoapp/init.py

put this code, I will explain it in comments:

from eve import Eve # imports eve.
from .extensions import assets # imports our assets extension
from .frontend import frontedbp # imports our frontend blueprint


# the factory function to actually create the app,
# this a Flask best practice described here http://flask.pocoo.org/docs/0.12/patterns/appfactories/
# if you want to make multiple processes to use the app
# it should be fairly easy
def create_app(config):
    # create the app

    app = Eve(__name__) # creates an app
    # since Eve is a Flask you can expect it to behave
    # like the Flask class

    # this means you can register the config like this
    app.config.from_object(config)

    # and register extensions
    register_extensions(app)

    # and also blueprints
    register_blueprints(app)
    return app


def register_extensions(app):
    # init the app the extension with app object
    assets.init_app(app)


def register_blueprints(app):
    # register the frontend blueprint to the application
    app.register_blueprint(
        frontedbp
    )

What about the assets extension, well this is not necessary but I wanted to use it because I thought of writing the frontend in coffeescript anyways you would need it in production, all the cool kids use webassets.

You need to install the flask assets extension

(venv) $ pip install flask-assets

so here is the extensions.py file, we only have one extension but anyways

from flask_assets import Environment

assets = Environment()

that's it, it just creates an environment which we will register bundles later.

Now the blueprint code, we have only one views file since there is no actual business logic so no models needed.

from flask import Blueprint, render_template
from todoapp.extensions import assets
from flask_assets import Bundle

# make a js bundle with the js files we will use and set, the output to gen/app.js
# we will also use the jsmin filter
js = Bundle(
    'https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js',
    'https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js',
    'js/infinite-scroll.js',
    'https://cdn.jsdelivr.net/lodash/4.17.4/lodash.js',
    'js/app.js',
    filters='jsmin',
    output='gen/app.js'
)

assets.register(
    'jsall',
    js
    )

# make a css bundle with the js files we will use and set, the output to gen/app.css
# we will also use the cssmin filter

css = Bundle(
    'css/app.css',
    'https://fonts.googleapis.com/css?family=Roboto:400,500,700',
    'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css',
    filters='cssmin',
    output='gen/app.css'
)

assets.register(
    'cssall',
    css
)

# make the blueprint
mybp = Blueprint(
    'frontedbp',
    __name__
)

# and serve the index.html file.
@mybp.route('/')
def index():
    return render_template(
        'index.html'
    )

You also need to install jsmin and cssmin packages to actually minify your css and js files.

(venv) $ pip install cssmin jsmin

in the todoapp/frontend/init.py

we will expose the frontend blueprint object

from .views import mybp as frontedbp # noqa

as for the configuration file, there is no real config, but I used this approach since it's a best practice for bigger apps.

class Config:
    """Documentation for Config
    """
    SECRET_KEY = 'something-really-secret'


class devConfig(Config):
    # set assets debug to true that webassets does not make our static files
    ASSETS_DEBUG = True
    # debug mode is always handy though we won't need it ;)
    DEBUG = True

class prodConfig(Config):
    # just to make sure when an idiot messes with
    # our Config class
    ASSETS_DEBUG = False
    DEBUG = False

so in the autoapp.py we should expose the app

from todoapp import create_app
from todoapp.config import devConfig

app = create_app(devConfig)

for development I like to use flask-script so you may want to install it

(venv) $ pip install flask-script

and in the manage.py file we will have

from flask_script import Manager
from todoapp.config import devConfig
from todoapp.extensions import assets
from flask_assets import ManageAssets
from autoapp import app


manager = Manager(app)
# add the assets sub command
manager.add_command("assets", ManageAssets(assets))


if __name__ == '__main__':
    manager.run()

Here I'm adding the assets command so you can watch your files, build them and clean the directory.

As you may have seen the python part is very thin, all the logic will happen in the views.

Writing the frontend

I didn't talk about the base.html and index.html didn't I?

let's make the base.html

<html>

<head>

{% assets  "cssall" %}
<link href="{{ ASSET_URL }}" rel="stylesheet"/>
{%endassets%}

</head>
{% block content %}

{% endblock %}

{% assets "jsall" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}

</html>

Remember when we registered our Bundles, well the assets tags just includes them in our base.html, so here we will include the css and js files.

We will reuse the content block in the index.html, I forgot to mention that {# #} is a block comment in jinja.

{% extends 'base.html' %} {# "inherit" the base template  #}

{% block content %} {# overwrite the content block #}
    <div id="todoapp"> {# this would be our view model #}
        <div id="searchbar">
            <input v-model="searchfield" name="" type="text" placeholder="Search todos..."/>
            <button><i class="fa fa-search" aria-hidden="true"></i></button> {# Meh we won't use this button but I like buttons :)#}
        </div>

    <div id="content">
        <div id="addtodo">
            {# so on when we press enter this should call the send method, and we will link this input with the todotext view model #}
            <input name="" v-on:keyup.enter="send"  type="text" placeholder="Add a todo .." v-model="todotext"/>
            <button v-on:click="send"><i class="fa fa-plus" aria-hidden="true"></i></buttton>
        </div>

        {# We will also use infinite scroll #}
        <div id="todos"  v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
            {# and we will repeat this Vue component for each each todo in our todos object, also on removeit event we will call the removeit method and on doneundone we will call toggledone method #}
            <todoitem v-for="(todo, index) in todos1" v-bind:todo="todo"
                      v-on:removeit="removeit(todo, index)"
                      v-on:doneundone="toggledone(todo, index)">
            </todoitem>
        </div>
    </div>

</div>
{% endblock %}

nice so let's spice this up a little and add some css, I'm pretty bad in css and design so excuse me.

add this to your app.css, css code won't need explaining I guess.

html {
    font-family: 'Roboto';
}

body {
    padding: 0; margin: 0;
    Background-color: #FAFAFA;
}

#searchbar {
    position: relative;
    height: 50px;
    background-color: #B0BEC5;
    padding: 15px 40px 10px 40px;
}

#searchbar input {
    width: 100%;
    border-radius: 10px;
    border-bottom-right-radius: 20px;
    border-top-right-radius: 20px;
    height: 40px;
    border: none;
    box-shadow: none;
    outline: 0;
    line-weight: 400;
    font-size: 16px;
    text-indent: 14px;

}

#searchbar button {
    position: absolute;
    outline: 0;
    top: 15px;
    right: 40px;
    height: 40px;
    width: 40px;
    border-radius: 50%;
    border: 0;
    Background-color: #4E342E;
    color: #FFF;
}


#content {
    margin: 40px 40px 40px 40px;
}

#content #addtodo input {
    width: calc(100% - 33px);
    outline: 0;
    border: 1px solid #90A4AE;
    height: 30px;
    border-radius: 20px;
    text-indent: 14px;
}

#content #addtodo {
    position: relative;
}


#content #addtodo button {
    position: absolute;
    height: 30px;
    width: 30px;
    right: 0px;
    border-radius: 50%;
    border: 0;
    background: #8bc34a;
    color: #FFF;
    outline: 0;
}



#todos .todo:last-of-type {
    margin-bottom: 0px;
}

#todos .todo {
    position: relative;
    margin-top: 20px;
    margin-bottom: 20px;
    Background-color: #FFF;
    border: 1px solid rgba(0, 0, 0, 0.3);
    min-height: 40px;
    padding-left: 15px;
}

#todos .todo button {
    position: absolute;
    height: 40px;
    width: 40px;
    top: 4px;
    Background: none;
    border: none;
    outline: 0;
}

#todos .todo .delete {
    right: 51px;
    color: red;
    border-radius: 50%;
    font-size: 15px;
    border: 1px solid red ;
    transition: 0.5s ease;

}


#todos .todo p {
    margin-right: 90px;
}

#todos .todo .done {
    right: 4px;
    color: green;
    border-radius: 50%;
    font-size: 15px;
    border: 1px solid green ;
    transition: 0.5s ease;
}


#todos .todo .done:hover {
    Background-color: green;
    color: white;
}

#todos .todo .delete:hover {
    Background-color: red;
    color: white;
}


#todos .todo .notdone {
    right: 4px;
    border-radius: 50%;
    font-size: 15px;
    border: 1px solid green ;
    transition: 0.5s ease;
    Background-color: green;
    color: white;
}


#todos .todo .notdone:hover {
    color: green;
    Background-color: white;
}

now let's write the javascript, nothing special we will just use good old js.

add this to your app.js file ( using a two spaces indent for the js, 4 is too much for me).

var todovm = new Vue({
  el: "#todoapp", // init Vue with this variables
  data: {
  searchfield: '',
  nexthref: '',
  todotext: '',
  todos1: [],
  busy: false,
},

// so watching any changes in the search input
watch: {
  searchfield: function (searchexpr) {
    // if anything changes in the search input just call the search method
    this.search(searchexpr);
  }
},

methods: {

  search: function (searchexpr) {
    // i don't know but js I can't seem to be able access any attribute or method inside a promise
    var vm = this;
    // if the search expression is empty just init the todos1 object and return to the caller
    if (searchexpr == '') {
      init();
      return ;
    }
    axios("/api/v1/todos?where={\"todotext\": { \"$regex\": \"" +  searchexpr + "\"}}")
      .then(function (response){
        vm.todos1 = response.data._items;
        if (response.data._links.next) {
          vm.nexthref = response.data._links.next.href;
        } else {
          vm.nexthref = false;
        }
      });
  },

  // remember when made our infinite scroll object
  loadMore: function() {
    var vm = this;
    // so if we have a next href
    if (vm.nexthref) {
      // we will busy for a second
      vm.busy = true;
      setTimeout(() => {
        if (vm.nexthref) {
          // send a get request and get the next page
          axios('/api/v1/' + vm.nexthref)
            .then (function (response) {
              // if the response is successful, concat the todos to our todos1 object, vuejs will automatically update
              // the dom
              vm.todos1 = vm.todos1.concat(response.data._items);
              if (response.data._links.next) {
                vm.nexthref = response.data._links.next.href;
              } else {
                vm.nexthref = false;
              }
            });
         }
     // not busy anymore
     vm.busy = false;
    }, 1000);
   }
  },

// send to api
send: function (event) {
  vm = this;
  // to register a new todo
  axios.post('/api/v1/todos',
     {
       todotext: this.todotext
     }, {
       headers: {
         "Content-Type": "application/json"
       }})
.then (function (response) {
  axios('/api/v1/' + response.data._links.self.href)
    .then(function (response) {
      // if the response is successful just add just add our todo object to the first positon
      // in the array
      vm.todos1.splice(0, 0, response.data);
    });
})
.catch(function (error) {
  console.log(error);
});
},

// to actually remove a todo
removeit: function (todoobject, index) {
  vm = this
  axios.delete(
'/api/v1/' + todoobject._links.self.href, {
  headers: {
    "If-Match": todoobject._etag // I will explain this later
  }
}
  )
.then(function () {
  //  if the remove is successful no need to reupdate the whole object
  // just remove it from the todos1 array
  vm.todos1.splice(
    index,
    1
  );
});
},

toggledone: function (todoobject, index) {
  vm = this;
  axios.patch(
  '/api/v1/' + todoobject._links.self.href,
{
  done: !todoobject.done
},
{
  headers: {
    "If-Match": todoobject._etag, // I will explain this later
    "Content-Type": "application/json"
  }
}
  )
.then(function (response) {
  axios('/api/v1/' + response.data._links.self.href)
    .then(function (response) {
      // using the set function since when using the index method vuejs won't know
      // if the object has changed.
      vm.$set(vm.todos1, index, response.data);
    });

});
},


}});

I think the code is not cryptic and fairly understandable.

Now I will explain why I passed the If-Match header to our API when sending a PATCH and DELETE request, the reason is to provide better concurrency control, now imagine this scenario, imagine that Alice and Mohamed are browsing the web app what if Alice un-done the task and Mohamed also wants to undone the task at the same time T or they didn't refresh they're pages, so they don't know the changes that the other part makes, well we provide the etag property which is a hash calculated in function of the data, so when we send an edit or delete request we also send some code that identifies the piece of data we are referring to.

Now let's define the todoitem component shall we?

append this to you app.js

Vue.component('todoitem', {
  template: `
    <div class="todo">
      <p> {{ todo.todotext }}</p>
      <button class="delete" v-on:click="$emit('removeit')"> // when clicking on the remove
        // button emmit a removeit event which we will catch later
        <i class="fa fa-trash-o" aria-hidden="true"></i>
      </button>
     <button v-bind:class="getstate" v-on:click="$emit('doneundone')">
         // when clicking on the done-undone button emmit a doneundone event
         <i class="fa fa-check" aria-hidden="true"></i>
      </button>
     </div>`,
  props: ['todo'], // this component will have on property which we binded in the html file

  computed : {
    getstate: function () {
      // returns the state of a todo, it's actually in reverse
      // so I didn't have to touch my css files, silly me..
      return (this.todo.done ? 'notdone' : 'done');
    }
  }});

// we will also call init function
init();

Now you should be able to run your app on your local machine using:

(venv) $ python manage.py runserver

Deploying your app

Deploying your application shouldn't be much of a problem, you can use a PaaS like heroku, anyways you can deploy it yourself if you insist to.

So when deploying there are tons of options, I myself will stick to Gunicorn.

So we need to install gunicorn, it should be as easy as

(venv) $ pip install Gunicorn

Installing Nginx is up to you.

Now let's create a wsgi.py file

from todoapp import create_app
from todoapp.config import prodConfig

app = create_app(prodConfig)

Now make your assets files using,

(venv) $ python manage.py assets build

Now you should be able to run

(venv) $ gunicorn -b 0.0.0.0:8000 wsgi:app

Normally you don't run your app directly instead you use a reverse proxy like Nginx.

so in your /etc/nginx/sites-enabled

add some file, in which you have something like this:

server {
    listen 80;
    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
    location / {
        proxy_pass         http://127.0.0.1:8050/;
        proxy_redirect     off;
        proxy_set_header   Host                 $host;
        proxy_set_header   X-Real-IP            $remote_addr;
        proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto    $scheme;
    }

    location /static {
        alias /your/static/files/path;
        expires 30d;
        add_header Vary Accept-Encoding;
        access_log off;
    }
}

Generally you always want to serve your files using Nginx, like that you can use caching, and remeber that webassets adds that hash at the end, which is calculated in function of the state of your static files so when ever you change your static files that hashes changes and therefore the users browser is obliged to download the file, thinking it's a new file, this is an old hack people use.

That's basically it, if you want more info about how I deploy my apps, see this page

I spent maybe half a day (or half a night) writing and preparing for this tutorial, so any feedback is welcome, Thanks.

Comments powered by Disqus