Getting Started

In this section we will explore building a complete webapp with Cob. We will cover the most frequent tasks often tackled when starting an app from scratch, and will also cover how to deploy your app once it’s ready for prime time.

Our examples will focus around building a TODO app, which will have all the parts a modern full-stack app has: a modern front-end UI, a backend for API calls, static files and assets and database integration.

First Steps

Prerequisites

Cob requires Python 3.6 in order to run, so you’ll have to install it on your system. For docker-related operations such as building images, pushing them etc., you’ll also need docker and docker-compose installed. Due to format changes made in docker-compose in the past, you’ll need version 1.13 or greater of docker-compose.

For deployment requirements, see the relevant section in the deployment documentation.

Installing Cob

To get started with cob, you will need to have cob installed on your system. Cob is basically a Python package, and as such - is most easily installed via pip:

$ pip install cob

The remainder of this tutorial will make extensive use of the cob command, which is the entry point for the various operations we will be using.

Creating your Project

Any Cob project lives in its own directory with a conventional structure. Although an empty project is very simple and minimalistic, Cob helps us create it through the cob generate command. cob generate always receives the type of thing we want to generate (in this case a project), and the name of what we’re creating:

$ cob generate project todos

The above command will create a new project directory called todos which we are going to use for our project. A minimalistic cob project is just a directory with a special YAML file, called .cob-project.yml.

We will be returning to this file very soon enough, so don’t worry.

Tip

Now would be a good time to start tracking your project with git:

$ cd todos
$ git init
$ git add .
$ git commit -m 'Initial version'

Creating a Simple Backend

Our application will need a backend serving our JSON API for fetching and updating TODO items. Cob is built around the Flask web framework and leverages its composability, while removing the need for boilerplate code.

Let’s start by adding our route for serving TODO items. Let’s create a file named backend.py, and add the following content to it:

# cob: type=views mountpoint=/api

from cob import route
from flask import jsonify

@route('/todos')
def get_all():
    return jsonify({'data': []})

Let’s break down the example to see what’s going on. The first line is a special one, used by cob to let it know how this file should be dealt with. It tells Cob that the type of this file is “views” (meaning a collection of Flask view functions), and that it should be “mounted” under /api in the resulting webapp.

Next, we import route from Cob. This is a shortcut to the app.route or blueprint.route in Flask, but is managed by Cob, and allows it to “discover” the routes that each file provides.

Running the Test Server

Before we move on, let’s give our little app a try. Go into your project directory and run cob testserver. If this is the first time you’re doing this in your project, Cob will initialize the private project environment for you first. For more information about how this works you can read about it here.

After the setup is done, your server will run on the default port:

$ curl http://127.0.0.1/api/todos
{
    "data": []
}

Note

cob testserver only runs the Flask backend serving your routes. Later on we will see how to set up a more complete testing environment

Interlude – What Have We Just Created?

Files of the like of backend.py above are very critical in Cob’s functionality. Cob takes away the boilerplate in your project – but in order to do so it needs to know the parts that constitute your project. Each such part is loaded separately, and its dependencies are automatically taken care of by cob.

These “pieces” of your project are called grains in cob. Each grain has a type (for instance we have just created a views grain), telling Cob how to handle it. As we move forward we will meet more types of grains that we can use in Cob.

Working with Data

Most web applications work on data, usually in the form of records in a database. This is one of the most “boilerplate”-ish tasks in backend development, so naturally Cob aims at simplifying it as much as possible.

Cob takes care of loading models from your project, and also takes care of connecting to your database and migrations. Let’s take a coser look at how it’s done as we improve our app to actually keep track of todo tasks.

Adding Models

Our first step will be to add a model for our Todos. We’ll use cob generate again to generate our models file:

$ cob generate models

This will create a file named models.py in our project directory. The file already imports the db component of cob, onto which we can define models:

$ cat models.py
# cob: type=models
from cob import db

Models grains use Flask-SQLAlchemy for defining models. Let’s create our task model:

...
class Task(db.Model):

    id = db.Column(db.Integer, primary_key=True)
    description = db.Column(db.Text, nullable=False)
    done = db.Column(db.Boolean, default=False)

Note

By default, Cob uses an SQLite database located in the project’s .cob directory for development, and switches to use Postgres for production (e.g. when being deployed as a docker container). In some cases you may want to use Postgres during development as well. In such cases, make sure you have a local Postgres instance running, and change the relevant DB URL to point at it. This can be done by adding the following to .cob-project.yml under your project root directory:

flask_config:
    SQLALCHEMY_DATABASE_URI: postgres://localhost/your_db

Initializing Migrations

We would like Cob to manage migrations for us, which will be useful when the time comes to modify and evolve our app, even after it’s already deployed. Cob allows us to easily create a migration for our data. First we will initialize the migrations data (only needs to happen once):

$ cob migrate init

Then we will create our automatic migration script:

$ cob migrate revision -m "initial revision"
$ cob migrate up

Note

cob migrate uses Alembic for migration management. you can refer to Alembic’s documentation for more information on how to customize your migration scripts

Note

Cob is configured, by default, to use an sqlite file under .cob/db.sqlite. See the database section to learn more on how it can be configured

Using Models in our Backend API

Using models is very simple now that we have defined our model. Let’s go back to our backend.py file and modify it to load and store our tasks from the database:

# cob: type=views mountpoint=/api

from cob import route
from flask import jsonify, request

from .models import db, Task


@route('/tasks')
def get_all():
    return jsonify({
       'data': [
           _serialize(task)
           for task in Task.query.all()
       ]})

@route('/tasks', methods=['POST'])
def create_todo():
    data = request.get_json()['data']
    task = Task(
        description=data['attributes']['description']
    )
    db.session.add(task)
    db.session.commit()
    return jsonify(_serialize(task))


def _serialize(task):
    return {
        'type': 'task',
        'id': task.id,
        'attributes': {
            'description': task.description,
        }
    }

We now have a working, simple TODO app, with a REST API to add and view tasks.

Testing

Now that our app is beginning to grow some logic, it’s time to start adding tests. Cob makes testing easy with the help of pytest and several related tools.

Let’s add our first test – create a directory called tests under your project root, and create your first test file – let’s name it test_todo.py:

def test_add_todo(webapp):
    message = 'some message'
    webapp.post('/api/tasks', data_json={
        'data': {
            'attributes': {
                'description': message,
            }
        }})
    all_todos = webapp.get('/api/todos')['data']
    last_todo = all_todos[-1]['attributes']
    assert last_todo['description'] == message

We wrote a single test function for use in pytest, with a single fixture called webapp, which is an instance of cob.utils.unittest.Webapp, a helper Cob exposes for tests.

To run our tests, all we need to do is run cob test from our project root.

Tip

cob test is just a shortcut for running pytest in your project. All options and arguments are forwarded to pytest for maximum flexibility.

Adding Third-Party Components

Cob is aimed at being the backbone of your webapp. Most web applications eventually need to bring in and use third party components or libraries, and Cob makes that easy to do.

We are going to improve our Todo app by using a third-part tool for serialization, marshmallow. The first order of business is to get Cob to install this dependency whenever our project is being bootstrapped. This can be done easily by appending it to the deps section of .cob-project.yml:

# .cob-project.yml
...
deps:
    - marshmallow

Now we can use this library to serialize our data, for instance create a file named schemas.py with the following:

from marshmallow import Schema, fields, post_dump, post_load, pre_load
from .models import Task


class JSONAPISchema(Schema):

    @post_dump(pass_many=True)
    def wrap_with_envelope(self, data, many): # pylint: disable=unused-argument
        return {'data': data}

    @post_dump(pass_many=False)
    def wrap_objet(self, obj):
        return {'id': obj.pop('id'), 'attributes': obj, 'type': self.Meta.model.__name__.lower()}

    @post_load
    def make_object(self, data):
        return self.Meta.model(**data)

    @pre_load
    def preload_object(self, data):
        data = data.get('data', {})
        returned = dict(data.get('attributes', {}))
        returned['id'] = data.get('id')
        return returned


class TaskSchema(JSONAPISchema):
    id = fields.Integer(dump_only=True)
    description = fields.Str(required=True)
    done = fields.Boolean()

    class Meta:
        model = Task

tasks = TaskSchema(strict=True)

And use it in backend.py:

from cob import route
from flask import jsonify, request

from .models import db, Task
from .schemas import tasks as tasks_schema

@route('/tasks')
def get_all():
    return jsonify(tasks_schema.dump(Task.query.all(), many=True).data)

@route('/tasks', methods=['POST'])
def create_todo():
    json = request.get_json()
    if json is None:
        return "No JSON provided", 400

    result = tasks_schema.load(json)
    if result.errors:
        return jsonify(result.errors), 400
    db.session.add(result.data)
    db.session.commit()
    return jsonify(tasks_schema.dump(result.data).data)

Building a UI

Cob makes it easy to integrate front-end code in the same repository as your backend, and helps you build, test and deploy it too.

Setting Up

In our example we will be using Ember to use our UI. We’ll start by creating our front-end grain:

$ cob generate grain --type frontend-ember webapp

Note

In order for the above to work, you need to have Ember CLI installed on your system

This will bootstrap your webapp subdirectory with our UI code, and take care of initial setup.

While this looks like black magic, what happens here is quite simple - Cob creates a directory called webapp, and places a .cob.yml file inside it, letting Cob know that this is a grain of type frontend-ember:

# In webapp/.cob.yml
type: frontend-ember

The .cob.yml file is just a different way to write the markup we used in the first comment line of our previous grains. Once we marked our webapp directory in this way, Cob knows how to treat it as one containing Ember front-end code.

Writing our Front-end Logic

We won’t dive in to how to develop using Ember, so we’ll just create a minimal front-end app for displaying and adding our TODOs.

Note

We won’t cover Ember here in depth – for more information you can refer to the excellent Ember Guides. For now, just take our word for it

// webapp/app/routes/tasks.js
import Ember from 'ember';

export default Ember.Route.extend({

    model() {
        return this.store.findAll('task');
    },

    setupController(controller, model) {
        this._super(...arguments);
        controller.set('tasks', model);
    },
});
// webapp/app/controllers/tasks.js
import Ember from 'ember';

export default Ember.Controller.extend({

    new_task: '',

    actions: {
        add_task() {
            let task = this.store.createRecord('task', {
                description: this.get('new_task'),
            });
            task.save();
        }
    }
});
// webapp/app/models/task.js
import DS from 'ember-data';

export default DS.Model.extend({

    description: DS.attr(),
    done: DS.attr('boolean'),
});
// webapp/app/adapters/application.js
import DS from 'ember-data';

export default DS.JSONAPIAdapter.extend({
    namespace: '/api',
});

And finally our template:

<!-- webapp/app/templates/tasks.hbs -->
{{#each tasks as |task| }}
  <div class="task">
    <h3>{{task.description}}</h3>
  </div>
{{/each}}

{{input value=new_task}}
<button {{action "add_task"}}>Add</button>

Developing Front-end and Backend Simultaneously

Now that we have multiple components to track during development (our Flask app and our Front-end compilation) we can make use of yet another handy tool Cob provides for us: cob develop:

$ cob develop

This command will fire up tmux (you’ll have to have it installed beforehand though), with two windows – one for running the backend server and the other running ember build --watch to compile your front-end. Cool huh?

Deploying your Application

Cob uses Docker for deploying apps. It is the best way to guarantee reproducible, composable setups and also allow reuse between development and deployment.

Cob separates deployment to two stages: building your deployment image and running it.

Building your Application Image

From your project directory, run:

$ cob docker build

This will create a basic Docker image, labeled todos by default (Cob uses the app name from the project’s configuration to name to label its images), and that image will be later on used for running your app.

Running your Application in Deployment

To run your app via docker-compose, running its various pieces properly linked, run:

$ cob docker run

This will start your app in the foreground.

See also

For more information on deploying your apps with Cob, see the Deployment section of the docs