Full-stack tutorial — 2 : Flask + mongoDB

Part II : Connecting MongoDB & CRUD operations on database.

Riken Mehta
7 min readApr 22, 2018

MongoDB is an open-source & document oriented NoSQL database. It uses JSON-like documents with schema. It is very powerful for quick & migration free development. To know more about MongoDB, check this very detailed tutorial. We will use MongoDB as our database & connect it with our flask web application.

Throughout this tutorial series, you will be reading about building a full-stack web application using flask + react + docker:

In this tutorial series we will eventually build a to-do application with user login & live update of the data. We will be using react as our front-end JavaScript library & MongoDB as our database.

In this — Part II — tutorial, we will update the basic flask server we have setup in Part I. Throughout this post, we will cover :

  • Connect MongoDB with flask server
  • Write controller for CRUD operation on collection named users.
  • Modify docker-compose files to support mongoDB as a service.

Note: This is Part II of the tutorial series for full-stack web app development. If you haven’t go through the — Part I — yet, please consider going through it first, because we will be using many references to the code implemented in previous post.

Directory structure

Connect MongoDB with flask server :

Here we will update our — modules/app/__init__.py — file like this.

Let’s understand what every piece of code is doing.

''' flask app with mongo '''                       
import os
import json
import datetime
from bson.objectid import ObjectId
from flask import Flask
from flask_pymongo import PyMongo

These are all basic imports. We are using flask_pymongo to make the database connection object available in flask app context. Internally flask_pymongo module uses mongoDB’s python client api PyMongo.

class JSONEncoder(json.JSONEncoder):                           
''' extend json-encoder class'''
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
if isinstance(o, datetime.datetime):
return str(o)
return json.JSONEncoder.default(self, o)

Here we have extended the json-encoder class to support ObjectId & datetime data types used to store ‘_id’ & ‘time-stamp’ respectively in MongoDB. All the response needs to be converted to json string, to enable the cross-platform data interpretation. We convert the ObjectId & datetime to string.

app.config['MONGO_URI'] = os.environ.get('DB')                       mongo = PyMongo(app)                                               app.json_encoder = JSONEncoder

We also add mongo url to flask config, so that flask_pymongo can use it to make connection. The mongo object returned can now be used in all of our routes. At last, we modify the default json-encoder with the one we have extended.

Write controller for CRUD operation on collection named users.

We create a directory named ‘controllers’ inside ‘modules/app/’. This directory will contain all the routes of different database collection in their respective files. For example, we will create ‘users’ collection to store users data. All the routes to handle this data will be in ‘modules/app/controllers/users.py’ file.

Inside controllers directory, we will add __init__.py file like this.

''' all controllers for various collections of database '''
import os
import glob
__all__ = [os.path.basename(f)[:-3]
for f in glob.glob(os.path.dirname(__file__) + "/*.py")]

This will import all the routes in all the files inside controllers directory. When controllers module will be imported, it will automatically define all the routes.

Now, let’s see how we add CRUD operation in users.py file.

Again, let’s see what every piece of code is doing.

''' controller and routes for users '''                       import os
from flask import request, jsonify
from app import app, mongo
import logger

Here we import app & mongo object from app module. mongo object can be used to query into the database. General query goes like this :

mongo.db.<collection>.<query>

Where <collection> is collection name. For us its ‘users’ at the moment. <query> will be anything like ‘find’, ‘update’, ‘delete’ etc.

ROOT_PATH = os.environ.get('ROOT_PATH')
LOG = logger.get_root_logger(
__name__, filename=os.path.join(ROOT_PATH, 'output.log'))

We get the ROOT_PATH from environment variable, and define a logger with ‘output.log’ file in root path.

@app.route('/user', methods=['GET', 'POST', 'DELETE', 'PATCH'])
def user():
''' route read user '''
if request.method == 'GET':
query = request.args
data = mongo.db.users.find_one(query)
return jsonify(data), 200

We register a route with GET, POST, DELETE & PATCH methods allowed. Inside the routine, we check if the method is GET, we find the user from the database with query sent in request arguments, and return the user data with status code 200.

data = request.get_json()
if request.method == 'POST':
if data.get('name', None) is not None and data.get('email',
None) is not None:
mongo.db.users.insert_one(data)
return jsonify({'ok': True, 'message': 'User created
successfully!'}), 200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters!'}), 400

For all other request methods, we need to get the request data using :

data = request.get_json()

Next we check if the request data contains ‘name’ & ‘email’ of a user. If yes, we insert the user document and return success message. Else, we give a bad request response.

if request.method == 'DELETE':
if data.get('email', None) is not None:
db_response = mongo.db.users.delete_one({'email':
data['email']})
if db_response.deleted_count == 1:
response = {'ok': True, 'message': 'record deleted'}
else:
response = {'ok': True, 'message': 'no record found'}
return jsonify(response), 200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters!'}), 400

If the method is DELETE, we check if the data contains ‘email’, if yes, we delete the user matching that email address. We check the database query response if the number of deleted record is one or not. And we send the message accordingly. Here also if we don’t get ‘email’ in request data, we send bad request response.

if request.method == 'PATCH':
if data.get('query', {}) != {}:
mongo.db.users.update_one(data['query'], {'$set':
data.get('payload' , {})})
return jsonify({'ok': True, 'message': 'record updated'}),
200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters!'}), 400

Finally, the PATCH method is used to modify the user record. If the ‘query’ exists in request data, we modify the record matching that query with the ‘payload’ given in request data.

Modify docker-compose files to support mongoDB as a service :

Everything is setup from flask side to connect and get the API’s rolling. Now, we need to make some modifications in docker configurations, so that we can launch the mongoDB server as a server inside a docker container & still be able to preserve the data. Let’s start with a little modification in Dockerfile.

FROM python:2.7
ADD . /usr/src/app
WORKDIR /usr/src/app
EXPOSE 4000
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ENTRYPOINT ["python","index.py"]

Let me explain a bit this time what actually Dockerfile does. This file is used to launch the docker container. And the commands in every line of this file represents some action to be taken at the time of launch.

Docker takes the python:2.7 image FROM docker repository, ADD every file of the current directory into /usr/src/app directory of the container we are about to launch. Make the same directory the working directory and EXPOSE 4000 port to the host os. Now it installs the required python packages. And defines the ENTRYPOINT as the index.py file.

Let’s modify our docker-compose.yml file to support mongoDB as a service.

version: '3.5'
services:
web_dev:
build: .
ports:
- "4000:4000"
volumes:
- .:/app
environment:
- ENV=development
- PORT=4000
- DB=mongodb://mongodb:27017/todoDev
mongodb:
image: mongo:latest
container_name: "mongodb"
environment:
- MONGO_DATA_DIR=/usr/data/db
- MONGO_LOG_DIR=/dev/null
volumes:
- ./data/db:/usr/data/db
ports:
- 27017:27017
command: mongod --smallfiles --logpath=/dev/null # --quiet
networks:
default:
name: web_dev

In this file, we define a new service named mongodb, with latest mongo image. Set some environment variables to define data directory to be used by mongo server. Now, the crucial part is to add volumes. We mount the local data directory with the /usr/data/db of docker container. Everything else is usual mongod command.

requests==2.18.1
Flask==0.12.2
pymongo==3.4.0
flask_pymongo==0.5.1

Now the last step would be to add the additional flask_pymongo dependency in requirements.txt file.

Let’s just test what we have build so far. We will need a tool named postman to test our APIs. You can download it from here.

Start the app using docker-compose command.

docker-compose up --build

You can check in the browser on localhost:4000. It will serve the index.html file which we haven’t changed from our last part. We have added a few user related routes, to check those we will need a development tool named postman to make REST api requests to our server.

Here are all the api request examples & response from our server.

POST:

POST request.

GET:

GET request.

PATCH:

PATCH request.

DELETE:

DELETE request.

These are the expected response from our server. I hope you get the same if everything was perfect. Even if that is not, mention that in the comment section, and I will love to reply to your queries and all the doubts.

In next part, we will configure our flask server with authentication routes. We will use JSON web tokens to authenticate all of our routes. This will enable use to add user sign up & login support to our to-do web application. So, stay tuned till the next part & give your valuable feedback in the comments.

--

--

Riken Mehta

CS Graduate student, NYU. Image processing & Machine learning practitioner. Former Co-founder and algorithms lead at poshaQ — a fashion retail startup.