Building and Deploying a Flask REST API on Heroku(Part 1)

Prakhar S
7 min readNov 28, 2021
Entity Relationship Diagram . Image By Author

Further to the last post, where I built a simple Flask API using an in-memory database, I am going to build a full-fledged REST API with SQL database and deploy it on Heroku. The database will have 2 tables, “Director” and “Movies” as shown in the Entity-Relationship Diagram above. The API will expose endpoints for users to access these tables and perform basic CRUD(Create, Read, Update and Delete) operations.

I will be using Flask-RESTful, which is an extension for Flask that adds support for quickly building REST APIs and Flask-SQLAlchemy which adds support for SQLAlchemy in our API. SQLAlchemy is an Object Relational Mapper (ORM) for Python, which provides us with efficient and high-performing SQL database access.

In this article, I will discuss the API code itself, and in the next section, I'll go over deployment, as both these need an article of their own.

Let's start building the API then and you will see that with Flask, the process is quite simple.

Before starting I want to give an overview of how the code structure will look like :

Code Structure . Image by Author

All the code for this project will reside in the folder “FlaskAPI”. In the root folder, we will have the ‘app.py’ file, which will act as the gateway for our API. The file “db.py” will also be in the root folder and it will be used to initialise our SQLAlchemy database connection. The folder “venv” gets autogenerated when we set up the virtual environment for our project and we don’t need to change anything there. Finally, there are 2 subfolders under the root folder: “models” and “resources”.

The ‘models’ folder will contain the blueprints of the objects or resources that our API will deal with. These models are used to define the table schema in our database. The ‘resources’ folder on the other hand are the objects, based on the models defined in the ‘models’ folder, that our API serves to external users or applications.

Now we can start with writing the actual code. Cd to your project folder in the terminal and run the following commands

// install the virtual environment package, if not already installed
-> FlaskAPI pip install virtualenv
// rename virtualenv
-> FlaskAPI virtualenv venv
// activate virtualenv
-> FlaskAPI source venv/bin/activate // for mac and linux
-> FlaskAPI .\venv\Scripts\activate //for windows
// install the required libraries
(venv)-> FlaskAPI pip install flask-restful Flask-SQLAlchemy

These are all the libraries that we are going to need for this project. In the next steps, we will create the files and folders

(venv)-> FlaskAPI mkdir models resources
(venv)-> FlaskAPI touch app.py db.py

Now our folder structure will look similar to the diagram above.

Open db.py and enter the following lines :

from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()

This initialises an instance of our SQLAlchemy object we imported from the Flask-SQLAlchemy library. This can be used to define our models. Cd to the ‘models’ folder and create the files for our models

(venv)-> FlaskAPI cd models
(venv)-> models touch __init__.py director.py movie.py

The __init__.py file is required to convert the models folder into a package which can be then used by other packages or apps. We will define the model for our ‘director’ and ‘movie’ entities within the other two files. Lets start with movie.py :

// import the SQLAlchemy instance from the db.py filefrom db import db// Define a class for our "movie" resource, which extends SQLAlchemy // Model class. This tells SQLALchemy that this class is going to
// represent a database table.
class MovieModel(db.Model):// give the table name for the db. __tablename__ = 'movies'// define the schema for the db table id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
year = db.Column(db.Integer)
ratings = db.Column(db.Float(precision=1))
// Add the reference to directors table which we is defined below and specify the foreign key

director_id = db.Column(db.Integer, db.ForeignKey('directors.id'))

director = db.relationship('DirectorModel')
// define the constructor for initialisation the attributes of // the classdef __init__(self,name,year,ratings,director_id):
self.name = name
self.year = year
self.ratings = ratings
self.director_id = director_id
// define other methods which will be used by the resources using
// these classes
// a method to convert the output to json
def json(self):
return {'name': self.name, 'year': self.year,
'ratings':self.ratings}
// a class method for finding the movies by name @classmethod
def find_movie_by_name(cls,name):
return cls.query.filter_by(name=name).first()
// The above query is same as
// SELECT * FROM movies WHERE name=name LIMIT 1)


// a method for add and saving to db
def save_to_db(self):
db.session.add(self)
db.session.commit()
// a method for deleting from db

def delete_from_db(self):
db.session.delete(self)
db.session.commit()

We use classmethod in ‘finding_movie_by_name’ as this method is not particular to any one movie object, but is associated with the ‘MovieModel’ class.

Similarly we can make edit director.py to include following code :

from db import dbclass DirectorModel(db.Model):    __tablename__ = 'directors'    id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
movies = db.relationship("MovieModel")
def __init__(self,name):
self.name = name
def json(self):
return {'name': self.name, 'movies' : [movie.json() for movie in self.movies]}
@classmethod
def find_director_by_name(cls,name):
return cls.query.filter_by(name=name).first()
def save_to_db(self):
db.session.add(self)
db.session.commit()
def delete_from_db(self):
db.session.delete(self)
db.session.commit()

The director table consists of only ‘id’ and ‘name’ columns . But the json method in this case will return the name and all the movies directed by the director present in our database.

After defining the models, we will construct our resources using these models. These resources are what our APIs will expose or serve to the external world.

Create files ‘__init__.py’ , ‘movie.py’ and ‘director.py’ in the ‘resources’ folder and start by editing ‘movie.py’

//import the necessary libraries
from flask_restful import Resource, reqparse
// import the MovieModel from the models folder movie.py file
from models.movie import MovieModel
// Define a Movie Resourceclass Movie(Resource):// create a parser object using reqparse module from flask-restful
// This stores the data in the requests made to our api
parser = reqparse.RequestParser() // add the arguments to the parser object which will define what fields to look for in the request. parser.add_argument('year',
type=int,
required=True,
help="This field cannot be left blank")
parser.add_argument('ratings',
type=float,
)

parser.add_argument('director_id',
type=int
)
// Define the methods for CRUD operations def get(self,name): movie = MovieModel.find_movie_by_name(name)
if movie:
return movie.json()
return {'message': 'Movie not found'}, 404
def post(self,name):
if MovieModel.find_movie_by_name(name):
// if movie already exists , we dont have to add it again
return {"message": "An item with name {} already exists"}, 400
// else we parse the request using the parser object of the class data = Movie.parser.parse_args()
movie = MovieModel(name, **data)
try:
movie.save_to_db()
except:
return {"message" : "An error occured inserting the movie"}, 500

return movie.json(), 201

def delete(self,name):
movie = MovieModel.find_movie_by_name(name)
if movie:
movie.delete_from_db()
return {"message" : "Item deleted"}
def put(self,name):
data = Movie.parser.parse_args()
movie = MovieModel.find_movie_by_name(name)
if movie:
movie.year = data['year']
movie.ratings = data['ratings']
else:
movie = MovieModel(name, **data)
movie.save_to_db()
return movie.json()// Define a class to represent the list of movies. class MovieList(Resource):
//This class only needs to have get method to get all the //movies at once
def get(self):
return {'movies' : [movie.json() for movie in MovieModel.query.all()]}

Same way we can define our director resource. Edit the ‘resources/director.py’ with the following code :

from flask_restful import Resource
from models.director import DirectorModel
class Director(Resource): def get(self,name):
director = DirectorModel.find_director_by_name(name)
if director:
return director.json()
else:
return {'message': 'Director does not exist'}, 404
def post(self,name):
if DirectorModel.find_director_by_name(name):
return {'messsage': 'Director already exists'},400
director = DirectorModel(name)
try:
director.save_to_db()
except:
return {'message': 'Director could not be added'}, 500
return director.json(), 201
def delete(self,name): director = DirectorModel.find_director_by_name(name)
if director:
director.delete_from_db()
return {'message': 'Director deleted'}
else:
return {'message': 'Director does not exist'} , 400
class DirectorList(Resource):
def get(self):
return {'directors': [director.json() for director in DirectorModel.query.all()]}

So now our models and resources have been defined. All that we have to do is to bring everything together in our app.py file . Enter the following code in app.py file

// Import the required libraries
import os
from flask import Flask
from flask_restful import Api
// Import the resources
from resources.director import Director, DirectorList
from resources.movie import Movie, MovieList
//Import the database instance
from db import db
// create an instance of Flask app and create expose it as an Api
// using Api module from flask_restful
app = Flask(__name__)
api = Api(app)
// specify the path to the databaseapp.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'// Tell the app to create a database before the first request@app.before_first_request
def create_tables():
db.create_all()
// Add the endpoints for our APIapi.add_resource(Director,'/director/<string:name>')
api.add_resource(Movie, '/movie/<string:name>')
api.add_resource(MovieList, '/movies')
api.add_resource(DirectorList,'/directors')
// Initialise the database
db.init_app(app)
// Run the app. The if condition tells python to run the app only
// when app.py is called directly, not when it being referenced or // imported in some other package
if __name__ == '__main__':
app.run(debug=True)

And our API is ready. We can run it on our local system and test it using Postman. In the next section we are going to deploy it on Heroku and test the endpoint generated from there.

The code for this section can be found on this link.

Thanks for reading. Any comments, suggestions or queries are most welcome.

--

--