The purpose of this post is to explain how to implement a simple REST API with Python, Falcon, and MongoDB, deploy, and run it inside a docker container.
· Prerequisites
· Overview
∘ What Is Falcon Web Framework?
∘ Why use Falcon for microservices?
· Let’s code
∘ MongoDB setup
∘ Set up Falcon
∘ Project Structure
∘ Create models
∘ Resources
∘ Configure WSGI server
· Build and Run the Service Using Docker Compose
· Test the REST API:
· Conclusion
· References
Prerequisites
- Docker (20.x.x or above)
- Docker-compose (2.5+)
- Python 3.5 or above
- Falcon 3.x.x
Overview
In this story, we are going to build a book API where the user can create a book, update the book, and fetch the book by id.
What Is Falcon Web Framework?
Falcon is a reliable, high-performance Python web framework for building large-scale app backends and microservices. It encourages the REST architectural style and tries to do as little as possible while remaining highly effective.
Why use Falcon for microservices?
Falcon provides some advantages that to other Python frameworks. (cf. full documentation)
- It is released under the terms of the Apache 2.0 License.
- It’s fast: For an extra speed boost, Falcon compiles itself with Cython when available, and also works well with PyPy.
- Reliable: Falcon does not depend on any external Python packages.
- Debuggable: To avoid incentivizing the use of a hard-to-debug global state, Falcon does not use decorators to define routes.
- Flexible: Due to Falcon’s minimalist design, Python community members are free to independently innovate on Falcon add-ons and complementary packages.
Let’s code
MongoDB setup
We are using docker-compose to easily set up a MongoDB instance with data persistent support.
version: '3'
services:
mongo:
image: mongo
ports:
- "27017:27017"
volumes:
- "mongodata:/data/db"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: monngoexample
MONGO_INITDB_DATABASE: falconapidb
volumes:
mongodata:
$ docker-compose up -d
Set up Falcon
We’ll do is install Falcon inside a fresh virtual environment. To that end, let’s create a new project folder called falcon-book-api, and set up a virtual environment.
$ mkdir falcon-book-api
$ cd falcon-book-api
$ virtualenv .venv
$ source .venv/bin/activate
$ pip install falcon
Project Structure
Our project structure will look like this:

For better organization, the architecture has been structured as follows
src/model: MongoDB’s data modelssrc/common: Common data and methodssrc/resource: inbound requests, validationdeployment: Deployment configuration files.test: Testing module
We install all the packages needed for the project using a requirements.txt file.

pip install -r requirements.txt
Create models
In models, we used MongoEngine ODM (Object Document Mapper). It offers a great deal of control & flexibility for working with MongoDB. Defining schemas for our documents can help iron out bugs involving incorrect types or missing fields, and also allows us to define utility methods on our documents in the same way as traditional ORMs.
from mongoengine import *
class Book(Document):
title = StringField(max_length=200, required=True)
price = FloatField()
description = StringField()
author = StringField(max_length=50, required=True)
Resources
Falcon follows the REST architectural style, meaning (among other things) that you think in terms of resources and state transitions, which map to HTTP verbs. For a resource to provide support for any HTTP method, we need to add an on_*() method to the class, where * is any one of the standard HTTP methods, lowercased (e.g., on_get(), on_put(), on_head(), etc.).
Supported HTTP methods are those specified in RFC 7231 and RFC 5789. This includes GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, and PATCH.
class BookResource(object):
def on_get(self, req, resp):
resp.status = falcon.HTTP_200
books = Book.objects()
resp.body = books.to_json()
def on_post(self, req, resp):
try:
resp.status = falcon.HTTP_201
book_data = req.media
# req.media will deserialize json object
book_obj = Book.objects.create(**book_data)
resp.body = json.dumps({
'message': 'book succesfully created!',
'status': 201,
'data': str(book_obj.id)
})
return
except Exception as e:
resp.status = falcon.HTTP_400
resp.body = json.dumps({
'message': str(e),
'status': 400,
'data': {}
})
return
def on_get_id(self, req, resp, book_id):
'''
:param book_id: book_id received in http path to query book object
:return:
'''
try:
book_obj = Book.objects.get(id=book_id)
# Query book collection to get a record with id = book_id
resp.body = book_obj.to_json()
resp.status = falcon.HTTP_200
except Exception as e:
resp.status = falcon.HTTP_404
resp.body = json.dumps({
'message': 'Book id does not exist.',
'status': 404,
'data': {}
})
Configure WSGI server
app.py source code is below.
import falcon
import mongoengine as mongo
from falcon_swagger_ui import register_swaggerui_app
import src.common.constants as constants
from src.common.cors import Cors
from src.common.handlers import ExceptionHandler as handler
from src.common.require_json import RequireJSON
from src.resource.book_resource import BookResource
# automatically creates a connection to the Mongodb database with the credentials supplied. This connection will be
# used throughout the application.
mongo.connect(
constants.MONGO['DATABASE'],
host=constants.MONGO['HOST'],
port=constants.MONGO['PORT'],
username=constants.MONGO['USERNAME'],
password=constants.MONGO['PASSWORD']
)
STATIC_PATH = pathlib.Path(__file__).parent / 'static'
# create your WSGI application and alias it as app.
app = falcon.API(middleware=[Cors(), RequireJSON()])
# route the HTTP path and method to the respective methods of the resource.
book = BookResource()
app.add_route('/api/book/', book)
app.add_route('/api/book/{book_id}', book, suffix="id")
app.add_static_route('/static', str(STATIC_PATH))
# global handler exception of application
app.add_error_handler(Exception, handler.handle_500)
# handler for not found resources
app.add_sink(handler.handle_404, '^((?!static).)*$')
register_swaggerui_app(app, constants.SWAGGERUI_URL, constants.SCHEMA_URL, page_title=constants.PAGE_TITLE,
favicon_url=constants.FAVICON_URL,
config={'supportedSubmitMethods': ['get', 'post', 'put', 'delete'], })
We can now start our application. We are using the Gunicorn server.
In development mode, run gunicorn --reload src.app:app
(Note the use of the --reload option to tell Gunicorn to reload the app whenever its code changes.)
Build and Run the Service Using Docker Compose
Supervisor is a system that allows to monitoring and control of the Gunicorn server.
deployment/gunicorn.conf
[supervisord]
nodaemon=true
[program:gunicorn_book]
command=gunicorn src.app:app --worker-class gevent -w 2 --bind 0.0.0.0:3000 --log-file=-
directory=/usr/api
stdout_logfile = /usr/api/logs/main.log
user=nobody
stdout_logfile_maxbytes=5MB
stdout_logfile_backups=50
stdout_capture_maxbytes=1MB
stdout_events_enabled=false
autostart=true
autorestart=true
redirect_stderr=true
loglevel=info
Full docker-compose file.
version: '3'
services:
mongo:
image: mongo
ports:
- "27017:27017"
volumes:
- "mongodata:/data/db"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: mongoexample
MONGO_INITDB_DATABASE: falconapidb
book-api:
build:
context: ./
dockerfile: ./Dockerfile
ports:
- '3000:3000'
volumes:
- ./:/usr/api
- ./logs/:/usr/api/logs
links:
- mongo
volumes:
mongodata:
Test the REST API:
Let’s run the API with Docker: docker-compose up -d
Documentation on swagger is available at localhost:3000/swagger

- POST: create a new book

Well done !!.
Conclusion
The complete source code is available on GitHub.
This post is an improved version of the original version that I published on https://dzone.com
You can reach out to me and follow me on Medium, Twitter, GitHub
Thanks for reading!