Paurakh Sharma Humagain
Posted on December 13, 2019
Part 1: Using MongoDB with Flask
Howdy! In the last Part of the series, we learned how to create a basic CRUD
REST API functionality using python list
. But that's not how the real-world applications are built, because if your server is restarted or god forbids crashes then you are gonna lose all the information stored in your server. To solve those problems (and many others) database is used. So, that's what we are gonna do. We are going to use MongoDB as our database.
If you are just starting from this part, you can find all the code we wrote till now here.
Before we start make sure you have installed MongoDB in your system. If you haven't already you can install for Linux, Windown and macOS.
There are mainly to popular libraries which makes working with MongoDB easier:
1) Pymongo is a low-level Python wrapper around MongoDB, working with Pymongo
is similar to writing the MongoDB query directly.
Here is the simple example of updating the name of a movie whose id
matches the given id
using Pymongo
.
db['movies'].update({'_id': id},
{'$set': {'name': 'My new title'}})
Pymongo
doesn't use any predefined schema so it can make full use of Schemaless nature of MongoDB.
2) MongoEngine is an Object-Document Mapper, which uses a document schema that makes working with MongoDB clear and easier.
Here is the same example using mongoengine
.
Movies.objects(id=id).update(name='My new title')
Mongoengine
uses predefined schema for the fields in the database which restricts it from using the Schemaless nature of MongoDB.
As we can see both sides have their advantages and disadvantages. So, choose the one that fits your project well. In this series we are going to learn about Mongoengine
, please do let me know in the comment section below if you want me to cover Pymongo
as well.
To work better with Mongoengine
in our Flask
application there is a great Flask
extension called Flask-Mongengine.
So, let's get started by installing flask-mongoengine
.
pipenv install flask-mongoengine
Note: Since flask-mongoengine
is built on top of mongoengine
it gets installed automatically while installing flask-mongoengine, also mongoengine
is build on top of pymongo
so, it also gets installed
Now, let's create a new folder inside movie-bag
. I am gonna call it database
. Inside database
folder create a file named db.py
. Also, create another file and name it models.py
Let's see how files/folder looks like now.
movie-bag
│ app.py
| Pipfile
| Pipfile.lock
└───database
│ db.py
└───models.py
Now, let's dive into the interesting part.
First of all, let's initialize our database by adding the following code to our db.py
#~movie-bag/database/db.py
from flask_mongoengine import MongoEngine
db = MongoEngine()
def initialize_db(app):
db.init_app(app)
Here we have imported MongoEngine
and created the db
object and we have defined a function initialize_db()
which we are gonna call from our app.py
to initialize the database.
Let's write the following code in our movie.py
inside models
directory
#~movie-bag/database/models.py
from .db import db
class Movie(db.Document):
name = db.StringField(required=True, unique=True)
casts = db.ListField(db.StringField(), required=True)
genres = db.ListField(db.StringField(), required=True)
What we just created is a document for our database. So, that the users cannot add other fields then what are defined here.
Here we can see the Movie
document has three fields:
1) name
: is a field of type String
, we also have two constraints in this field.
- required
which means the user cannot create a new movie without giving its title.
- unique
which means the movie name must be unique and cannot be repeated.
2) casts
: is a field of type list
which contains the values of type String
3) genres
: same as casts
Finally, we can initialize the database in our app.py
and change our view
functions (functions handling our API request) to use the Movie
document we defined earlier.
#~movie-bag/app.py
-from flask import Flask, jsonify, request
+from flask import Flask, request, Response
+from database.db import initialize_db
+from database.models import Movie
app = Flask(__name__)
-movies = [
- {
- "name": "The Shawshank Redemption",
- "casts": ["Tim Robbins", "Morgan Freeman", "Bob Gunton", "William Sadler"],
- "genres": ["Drama"]
- },
- {
- "name": "The Godfather ",
- "casts": ["Marlon Brando", "Al Pacino", "James Caan", "Diane Keaton"],
- "genres": ["Crime", "Drama"]
- }
-]
+app.config['MONGODB_SETTINGS'] = {
+ 'host': 'mongodb://localhost/movie-bag'
+}
+
+initialize_db(app)
-@app.route('/movies')
-def hello():
- return jsonify(movies)
+@app.route('/movies')
+def get_movies():
+ movies = Movie.objects().to_json()
+ return Response(movies, mimetype="application/json", status=200)
-@app.route('/movies', methods=['POST'])
-def add_movie():
- movie = request.get_json()
- movies.append(movie)
- return {'id': len(movies)}, 200
+@app.route('/movies', methods=['POST'])
+ body = request.get_json()
+ movie = Movie(**body).save()
+ id = movie.id
+ return {'id': str(id)}, 200
-@app.route('/movies/<int:index>', methods=['PUT'])
-def update_movie(index):
- movie = request.get_json()
- movies[index] = movie
- return jsonify(movies[index]), 200
+@app.route('/movies/<id>', methods=['PUT'])
+def update_movie(id):
+ body = request.get_json()
+ Movie.objects.get(id=id).update(**body)
+ return '', 200
-@app.route('/movies/<int:index>', methods=['DELETE'])
-def delete_movie(index):
- movies.pop(index)
- return 'None', 200
+@app.route('/movies/<id>', methods=['DELETE'])
+def delete_movie(id):
+ Movie.objects.get(id=id).delete()
+ return '', 200
app.run()
Wow! that a lot of changes, let's go step by step with the changes.
-from flask import Flask, jsonify, request
+from flask import Flask, request, Response
+from database.db import initialize_db
+from database.models.movie import Movie
Here we removed jsonify
as we no longer need and added Response
which we use to set the type of response. Then we import initialize_db
form db.py
which we defined earlier to initialize our database. And lastly, we imported the Movie
document form movie.py
+app.config['MONGODB_SETTINGS'] = {
+ 'host': 'mongodb://localhost/movie-bag'
+}
+
+db = initialize_db(app)
Here we set the configuration for our mongodb database. Here the host is in the format <host-url>/<database-name>
. Since we have installed mongodb locally so we can access it from mongodb://localhost/
and we are gonna name our database movie-bag
.
And at the last, we initialize our database.
+@app.route('/movies')
+def get_movies():
+ movies = Movie.objects().to_json()
+ return Response(movies, mimetype="application/json", status=200)
+
Here we get all the objects from Movie
document using Movies.objects()
and convert them to JSON
using to_json()
. At last, we return a Response
object, where we defined our response type to application/json
.
+@app.route('/movies', methods=['POST'])
+ body = request.get_json()
+ movie = Movie(**body).save()
+ id = movie.id
+ return {'id': str(id)}, 200
In the POST
request we first get the JSON
that we send and a request. And then we load the Movie
document with the fields from our request with Movie(**body)
. Here **
is called the spread operator which is written as ...
in JavaScript (if you are familiar with it.). What it does is like the name suggests, spreads the dict
object.
So, that Movie(**body)
becomes
Movie(name="Name of the movie",
casts=["a caste"],
genres=["a genre"])
At last, we save the document and get its id
which we return as a response.
+@app.route('/movies/<id>', methods=['PUT'])
+def update_movie(id):
+ body = request.get_json()
+ Movie.objects.get(id=id).update(**body)
+ return '', 200
Here we first find the Movie document matching the id
sent in the request and then update it. Here also we have applied the spread operator to pass the values to the update()
function.
+@app.route('/movies/<id>', methods=['DELETE'])
+def delete_movie(id):
+ Movie.objects.get(id=id).delete()
+ return '', 200
Similar to the update_movie()
here we get the Movie document matching given id
and delete it from the database.
Oh, I just remembered that we haven't added the API endpoint to GET
only one document from our server.
Let's add it:
Add the following code right above app.run()
@app.route('/movies/<id>')
def get_movie(id):
movies = Movie.objects.get(id=id).to_json()
return Response(movies, mimetype="application/json", status=200)
Now you can get the single movie from API endpoint /movies/<valid_id>
.
To run the server make sure you are at movie-bag
directory.
Then run
pipenv shell
python app.py
To activate the virtual environment in your terminal and start the server.
Wow! Congratulations on making this far. To test the APIs, use Postman
as we used in the previous part of this series.
You might have noticed that if we send invalid data to our endpoint e.g: without a name, or other fields we get an unfriendly error in the form of HTML
. If we try to get the movie document with id
that doesn't exist in the database then also we get an unfriendly error in the form of HTML
response. Which is not an excepted behavior of a nicely build API. We will learn how we can handle such errors in the later parts of the series.
What we learned from this part of the series?
- Difference between
Pymongo
andMongoengine
. - How to create Document schema using
Mongoengine
. - How to perform
CRUD
operation usingMongoengine
. - Python spread operator.
You can find the complete code of this part here
In the next part, we are going to learn how to better structure your flask application using Blueprint
. And also how to create REST APIs faster, following best practices with the minimal setup using flask-restful
Until then happy coding 😊
Posted on December 13, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.