Day 25: Tornado--Combining Tornado, MongoDB, and AngularJS to Build an App

Today for my 30 day challenge, I decided to take a break from JavaScript and learn a web framework called Tornado. I decided to learn Tornado so that I can write web applications in Python. I only know Flask framework so I thought Tornado would be a good addition to my Python web development skills. The application that we will write in this blog post will use Tornado for the REST backend, MongoDB as the database , AngularJS as the client side JavaScript MV* framework, and OpenShift as deployment platform.

Tornado logo

What is Tornado?

Tornado is an open-source Python Web framework and non blocking web server, originally developed at FriendFeed. After the FriendFeed acquisition, Facebook maintains and develops Tornado. It has excellent scalability characteristics due to its non-blocking network I/O nature and can scale to 1000's of simultaneous connections.

Application Usecase

In this blog post, we will develop a social bookmarking application which allows users to post and share links. You can view the live application running on OpenShift here. This is the same application which we developed on day 22 so please refer to the blog to better understand the application usecase.

Github Repository

The code for today's demo application is available on github: day25-tornado-demo-app.

Prerequisite

Before we can get started with Tornado, we need to install Python and virtualenv on the machine. The Python version I am using in this blog post is 2.7.

This application uses MongoDB as data storage choice so please download the latest MongoDB release for your operation system.

Developing Tornado MongoDB Application

We will use the pip install to get started with Tornado. For developers unaware of pip, it is Python package manager. We can install pip from the official website. Go to any convenient directory on your file system, and run following commands.

$ mkdir getbookmarks
$ cd getbookmarks
$ virtualenv venv --python=python2.7
$ . venv/bin/activate
$ pip install tornado
$ pip install pymongo

The commands above will create a getbookmarks directory on the local machine, then activate virtualenv with Python version 2.7, then install the tornado package, and finally install pymongo. The pymongo is the official MongoDB Python driver. We will use it to write stories into MongoDB.

Create a new file called getbookmarks.py under the getbookmarks folder.

$ touch getbookmarks.py

Copy the following code and paste it in the getbookmarks.py source file

import os
from tornado import ioloop,web
from pymongo import MongoClient
import json
from bson import json_util
from bson.objectid import ObjectId
 
class IndexHandler(web.RequestHandler):
    def get(self):
        self.write("Hello World!!")
 
settings = {
    "template_path": os.path.join(os.path.dirname(__file__), "templates"),
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "debug" : True
}
 
application = web.Application([
    (r'/', IndexHandler),
    (r'/index', IndexHandler),
],**settings)
 
if __name__ == "__main__":
    application.listen(8888)
    ioloop.IOLoop.instance().start()

The code shown above does the following :

  1. We start with importing the required libraries.

  2. Next, we defined a new class called IndexHandler which extends web.RequestHandler. A Tornado web applications maps URLs or URL patterns to subclasses of the web.RequestHandler class. These classes define get(), post(),etc. methods to handle HTTP GET or POST requests to that URL. When a GET request is made to the '/' url, then IndexHandler will respond with "Hello World!!".

  3. Then we defined some application settings. The template_path setting is to tell the Tornado application to look for application templates in templates directory. The static_path setting informs the application to use static directory for the static assets like css , images , and javascript files. We also enabled debugging by passing debug:True. The main benefit of the debugger is that it will automatically reload the changes. We can keep the debugger running in the background and work through our application. This provide highly productive environment.

  4. Next, we created the Tornado application instance passing it the routes and settings.

  5. Finally we start the server to run the application using python getbookmarks.py command.

Run the application using the command shown below. Go to http://localhost:8888 and http://localhost:8888/index to verify you see "Hello World!!".

$ python getbookmarks.py

Configure MongoDB

Add the following lines after importing the libraries. We defined MongoDB connection url and database name. If the application is deployed on OpenShift then OpenShift specific environment variables will be used otherwise local machine configuration will be used.

MONGODB_DB_URL = os.environ.get('OPENSHIFT_MONGODB_DB_URL') if os.environ.get('OPENSHIFT_MONGODB_DB_URL') else 'mongodb://localhost:27017/'
MONGODB_DB_NAME = os.environ.get('OPENSHIFT_APP_NAME') if os.environ.get('OPENSHIFT_APP_NAME') else 'getbookmarks'
 
client = MongoClient(MONGODB_DB_URL)
db = client[MONGODB_DB_NAME]

We created an instance of MongoClient passing it the connection url. This connects it to the running mongod instance. Next we get the database using MongoClient instance.

Create and List All Stories

Now we will add functionality to create new stories and list all the stories. We will first add the route to Application instance as shown below.

application = web.Application([
    (r'/', IndexHandler),
    (r'/index', IndexHandler),
    (r'/api/v1/stories',StoriesHandler),
],**settings)

Next we will define the StoriesHandler which will be responsible for persisting stories into MongoDB and finding all the stories.

class StoriesHandler(web.RequestHandler):
    def get(self):
        stories = db.stories.find()
        self.set_header("Content-Type", "application/json")
        self.write(json.dumps(list(stories),default=json_util.default))
 
 
    def post(self):
        story_data = json.loads(self.request.body)
        story_id = db.stories.insert(story_data)
        print('story created with id ' + str(story_id))
        self.set_header("Content-Type", "application/json")
        self.set_status(201)

In the code shown above

  1. When a user makes a GET request to '/api/v1/stories', then we make a find() call to MongoDB. As we are not specifying any query so it will fetch all the stories from MongoDB. We set the content type to "application/json" and dump the json response.

  2. When a user makes POST request to '/api/v1/stories', then we first decode the json body to a dictionary and then write the data to MongoDB. We set the response status to 201(Created).

View Individual Story

The last backend functionality is to view the individual story. We first specify the route.

application = web.Application([
    (r'/', IndexHandler),
    (r'/index', IndexHandler),
    (r'/api/v1/stories',StoriesHandler),
    (r'/api/v1/stories/(.*)', StoryHandler)
],**settings)

Then we will write the StoryHandler

class StoryHandler(web.RequestHandler):
    def get(self , story_id):
        story = db.stories.find_one({"_id":ObjectId(str(story_id))})
        self.set_header("Content-Type", "application/json")
        self.write(json.dumps((story),default=json_util.default))

The code shown above finds the story corresponding the story_id and then dumps the json response.

AngularJS Front End

I decided to reuse the AngularJS front end which we wrote on day 22. The day 22 showcased how we can use AngularJS with Java Spring framework backend. The best part of using JavaScript MV* frameworks is that you can reuse the frontend code if your application stick to the REST interface client needs. Please read the day 22 blog for more information.

You can download the AngularJS front end from my github repository. Copy the static and templates folder and place it next to the getbookmarks.py.

Deploying Application to OpenShift

Before we deploy the application to OpenShift, we'll have to do few setup tasks :

  1. Sign up for an OpenShift Account. It is completely free, and Red Hat gives every user three free Gears on which to run your applications. At the time of this writing, the combined resources allocated for each user is 1.5 GB of memory and 3 GB of disk space.

  2. Install the rhc client tool on the machine. The rhc is a ruby gem so you need to have ruby 1.8.7 or above on your machine. To install rhc, just typesudo gem install rhc If you already have one, make sure it is the latest one. To update the rhc, execute the command shown below.sudo gem update rhc For additional assistance setting up the rhc command-line tool, see the following page: https://openshift.redhat.com/community/developers/rhc-client-tools-install

  3. Setup the OpenShift account using rhc setup command. This command will help us create a namespace and upload your ssh keys to OpenShift server.

After setup, we can create a new OpenShift application by running the following command.

$ rhc create-app day25demo python-2.7 mongodb-2 --from-code https://github.com/shekhargulati/day25-tornado-demo-app.git

It will do all the stuff from creating an application, to setting up public DNS, to creating private git repository, and then finally deploying the application using code from my Github repository. The app is running here http://day25demo-shekhargulati.rhcloud.com/#/

That's it for today. Keep giving feedback.

What's Next