REST API with Flask-Restless and SQLAlchemy

This post shows how to auto-generate a REST API from a handful of SQLAlchemy models. This means full CRUD (POST, GET, PUT, DELETE) endpoint generation for each SQLAlchemy model defined.

Let’s say our company is building a Single-Page Application for librarians & readers that accomplishes the following 2 goals:

  1. Show the availability and information of a given book to readers.
  2. Allow a librarian to perform CRUD operations to update their libary’s inventory.

We’ve been tasked with building a REST API to allow a Single-Page Application to interface with our database.

Environment Setup

  1. Create project root directory.
    mkdir automagic_api
    
  2. Create and activate virtualenv.
    cd automagic_api;
    # virtualenv env; Python 2.7
    python -m venv env
    source env/bin/activate
    
  3. Install flask-restless and sqlalchemy
    pip install flask-restless sqlalchemy
    

Your project directory tree should look as follows:

automagic_api/
env/

Further compartmentalize the project and define our models

  1. Create nested project directory ‘/automagic_api’ to hold Flask-specific files.
    mkdir automagic_api
    cd automagic_api
    
  2. Create ‘models.py’ within the flask-project directory and define Book and Author..
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import ForeignKey, Column, Integer, String, Boolean
from sqlalchemy.orm import backref, relationship
from automagic_api import Base

class Author(Base):
    @declared_attr
    def __tablename__(cls):
        # API endpoint will take the form '/api/__tablename__'
        return cls.__name__.lower()

    id = Column(Integer, primary_key=True) 
    first_name = Column(String(64))
    last_name = Column(String(64))

class Book(Base):
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    id = Column(Integer, primary_key=True) 
    title = Column(String(64))
    author_id = Column(Integer, 
        ForeignKey("author.id"), nullable=True)
    author = relationship(Author, 
        backref=backref('books'))  
    is_available = Column(Boolean)


Your directory structure should now look like this:

automagic_api/
   automagic_api/
       models.py
   env/

Create controllers.py to define endpoints

Below we define our 2 Flask-restless API blueprints: one for Book and one for Author, which when instantiated, construct the CRUD endpoints for each respective model.

from automagic_api import app, s, manager
from automagic_api.models\
    import Book, Author

author_api_blueprint = manager.create_api_blueprint(Author,
        methods=['GET', 'PATCH', 'POST', 'DELETE'])
book_api_blueprint = manager.create_api_blueprint(Book,
        methods=['GET', 'PATCH', 'POST', 'DELETE'])

Your directory should now look like:

automagic_api/
   automagic_api/
       models.py
       controllers.py
   env/

Create __init__.py in our Flask-project

Create the file in ‘automagic_api/automagic_api/’. This file will import our models.py, our controllers.py and instantiate our API.

import flask
import flask_restless
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import *

app = flask.Flask(__name__)

# Create our SQLAlchemy DB engine
engine = create_engine('sqlite:///foobar.db')
Session = sessionmaker(bind=engine, autocommit=False, autoflush=False)
s = scoped_session(Session)

Base = declarative_base()
Base.metadata.bind = engine

# Import all models to add them to Base.metadata
from models import Book, Author

Base.metadata.create_all()

manager = flask_restless.APIManager(app, session=s)
# Register flask-restless blueprints to instantiate CRUD endpoints
from controllers import book_api_blueprint, author_api_blueprint
app.register_blueprint(author_api_blueprint)
app.register_blueprint(book_api_blueprint)

Create run.py

Create run.py in our root directory

from automagic_api import app
app.run(debug=True, host='0.0.0.0', port=5000)

Your final directory structure should look as follows:

automagic_api/
   run.py
   automagic_api/
       __init__.py
       models.py
       controllers.py
   env/

Start and test your API!

In the project root, start the server

env/bin/python run.py

In a python CLI, create a POST request to create a new Book:

>>> import requests
>>> import json
>>> payload = { 
    'title': "The Eye of the World",
    'author': {
        'first_name': "Robert",
        'last_name': "Jordan"
        },  
    'is_available': True
}
>>> headers = {'content-type': 'application/json'}
>>> r = requests.post("http://localhost:5000/api/book", data=json.dumps(payload),headers=headers)
>>> print r.status_code
201

Go ahead and open up a web browser, and navigate to http://localhost:5000/api/book to see the Book object that you just POSTed to your sqlite database.

Likewise, you can invert this request and check out http://localhost:5000/api/author to see the Author object that was created, and the nested “books” attribute that the author has written.

  • Note: URLs for the API are constructed based off of the __tablename__ of the sqlalchemy model, and are prefixed with “/api” (i.e Book -> “/api/book”)

Congratulations, you now have a working boilerplate for a SQLAlchemy-model defined REST API. Now go ahead and add more models and endpoints!

Final Remarks

The reason that I use Flask-restless for day-to-day APIs is it’s abstraction.

  1. It preserves abstraction between the database’s relational model, and the JSON schema expected as input and output to the API
  2. It preserves abstraction between SQLAlchemy, and the SQL dialect under-the-hood.

For instance, when I POST the following payload from a client application, I don’t care about the relational model between an Author and a Book, I just care that the JSON Book object contains a nested Author in its schema. By providing the nested Author object WITHOUT an id field, I implicitly CREATE this Author in the database.

{ 
    'title': "The Eye of the World",
    'author': {
        'first_name': "Robert",
        'last_name': "Jordan"
        },  
    'is_available': True
}

Conversely, if I would like to create a book that references a pre-existing author, I would changed my nested Author object to contain the id of the Author who wrote it.

{ 
    'title': "The Eye of the World",
    'author': {
         'id': 1   
     },
     // or, we could break abstraction and assign an 'author_id':
     // 'author_id': 1,
    'is_available': True
}

Regarding preservation of SQLAlchemy’s level of abstraction, Flask-restless goes as far as to implement SQLAlchemy’s Query API at the REST API level. Take the following example:

If Robert Jordan wrote 14 books (which he did in a painstakingly long series), and I only wanted to grab all books that Robert Jordan penned which are available in the library I could run the following query:

http://localhost:5000/api/book?q={"filters":[{"name":"is_available","op":"==","val":true},{"name":"author_id","op":"==","val":1}]}

This would return our targeted books, and translates to running the following in SQLAlchemy:

session.query(Book).filter(Book.author_id==1, 
    Book.is_available == True).all()

These 2 key features make this tool perfect for Single-Page Applications that need to communicate with an API server.

A huge thanks to the brilliant developers of Flask, Flask-restless and SQLAlchemy. I am but a humble messenger spreading the work of others…