Rest API development with Python, Flask, MongoDB, Docker | Part 1

Adnan Kaya
6 min readDec 6, 2022

--

In this tutorial series we are going to develop a rest api with python, flask, mongodb and docker.

Contents

  1. Installation and Project Structure
  2. Running the app and adding first view and endpoint
  3. Working with Flask, Mongoengine and MongoDB

Requirements

  1. Download python3.11
  2. Docker version 20.10.21
  3. Flask==2.2.2
  4. flask-mongoengine==1.0.0
  5. flask-marshmallow==0.14.0
  6. apifairy==1.3.0

Let’s create a project folder named as Flask_MongoDB_Docker .

When starting a new python project it is a good practice to create a virtual environment.

Let’s do it by typing the following command in the project folder

$ cd Flask_MongoDB_Docker/
# I named my virtual environment as env311
$ python3.11 -m venv env311
# Activate virtual environment (macos/linux users)
$ source env311/bin/activate
# if you are a windows user you can activate like this
# $ env311\Scripts\activate

Install Flask, Flask-MongoEngine and other dependencies

$ pip install flask
$ pip install flask-mongoengine
$ pip install python-dotenv
$ pip install flask-marshmallow
$ pip install apifairy

Project Files and Folders Structure

$ tree -I env311 Flask_MongoDB_Docker 

Flask_MongoDB_Docker
├── Dockerfile
├── README.md
├── api
│ ├── __init__.py
│ ├── app.py
│ ├── models.py
│ ├── schemas.py
│ └── views.py
├── config.py
├── docker-compose.yml
├── main.py
└── requirements.txt

1 directory, 11 files

Let’s start coding on config.py

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

BASE_DIR = os.path.abspath(os.path.dirname(__file__))

class Config:
# security options
SECRET_KEY = os.environ.get('SECRET_KEY', 'top-secret!')

# mongo db options will be added

After defining Config class, we are going to add a function/method on api/app.py to create the app instance.

# api/app.py
from flask import Flask

# internals
from config import Config

def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

return app

To be able to call create_app function let’s import it on api/__init__.py

# api/__init__.py
from api.app import create_app

Lastly we are going to create an app using create_app function on main.py

# main.py
from api import create_app

app = create_app()

To run the project create .env file inside the project folder. And define FLASK_APP variable like the following and also to activate debugger define FLASK_DEBUG variable.

# Flask_MongoDB_Docker/.env
FLASK_APP=main.py
FLASK_DEBUG=1

Now we can run our project on the terminal

# Flask_MongoDB_Docker/
$ flask run

You will see the output like

# output of "flask run" command

* Serving Flask app 'main.py'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 854-344-911

If you make a GET request to http://127.0.0.1:5000 you will get HTTP 404 Error. Because we did not create any endpoint(resource, path).

<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL
manually please check your spelling and try again.</p>

Let’s write our first endpoint on api/views.py

# api/views.py
from flask import Blueprint

books_bp = Blueprint("books", __name__)


@books_bp.route("/books/", methods=["GET"])
def books():
book_list = [
{"id":1, "name":"Python Flask & MongoDB", "author":"Adnan Kaya", "published_year": 2023},
{"id":2, "name":"Python for Absolute Beginners", "author":"Adnan Kaya", "published_year": 2025},
{"id":3, "name":"Learn Django by Developing Projects", "author":"Adnan Kaya", "published_year": 2024},
]

return book_list
  • We import Blueprint firstly
  • And create a Blueprint object by giving “books” name as parameter and __name__ as import name. This object assigned to books_bp variable.
  • We will use books_bp object while defining our routes(the term is called also endpoints or paths or resources)
  • We only allow GET request on “/books/” endpoint
  • We create a function named as books and this returns a list which contains 3 dict objects.

And now define books blueprint instance in the create_app method (in api/app.py)

# api/app.py
from flask import Flask

# internals
from config import Config

def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

# blueprints
API_URL_PREFIX = "/api/v1"
from api.views import books_bp

app.register_blueprint(books_bp, url_prefix=API_URL_PREFIX)

return app

If you make a GET request to http://localhost:5000/api/v1/books/ you will get the following response

[
{
"author": "Adnan Kaya",
"id": 1,
"name": "Python Flask & MongoDB",
"published_year": 2023
},
{
"author": "Adnan Kaya",
"id": 2,
"name": "Python for Absolute Beginners",
"published_year": 2025
},
{
"author": "Adnan Kaya",
"id": 3,
"name": "Learn Django by Developing Projects",
"published_year": 2024
}
]

Working with Flask, Mongoengine and MongoDB

Before we implementing mongoengine let’s run mongodb as a docker container. On terminal you can run it like following

docker run --name mongodb -d -p 27017:27017 -e  MONGO_INITDB_ROOT_USERNAME=developer -e MONGO_INITDB_ROOT_PASSWORD=developer mongo

This command will print out a container id like

ec1fc7b8439dfeb57cf06f93a0271935123cd1d1467be9e02bc4ece5b9046e77

Let’s check our docker container and open mongo shell (mongosh)

$ docker ps -a
# output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec1fc7b8439d mongo "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 0.0.0.0:27017->27017/tcp mongodb


##### Open mongo shell #####
$ docker exec -it mongodb mongosh -u developer -p developer

# if it is succeeded you will see
test>
# to see databases you can type `show dbs`
test> show dbs
admin 100.00 KiB
config 60.00 KiB
local 72.00 KiB

Now let’s add mongodb configurations for flask app on config.py

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

BASE_DIR = os.path.abspath(os.path.dirname(__file__))

class Config:
# security options
SECRET_KEY = os.environ.get('SECRET_KEY', 'top-secret!')

# mongo db options will be added
MONGODB_SETTINGS = [
{
"db": os.environ.get("MONGODB_DBNAME","mydb"),
"host": os.environ.get("MONGODB_HOST","localhost"),
"port": int(os.environ.get("MONGODB_PORT")) or 27017,
"alias": "default",
"username": os.environ.get("MONGODB_USERNAME","developer"),
"password": os.environ.get("MONGODB_PASSWORD","developer")
}
]

Also we can add these credentials into .env file

FLASK_APP=main.py
FLASK_DEBUG=1
MONGODB_DBNAME=mydb
MONGODB_HOST=localhost
MONGODB_PORT=27017
MONGODB_USERNAME=developer
MONGODB_PASSWORD=developer

Now instantiating MongoEngine on api/app.py

# api/app.py
from flask import Flask
from flask_mongoengine import MongoEngine # new

# internals
from config import Config

db = MongoEngine() # new

def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

# database mongodb
db.init_app(app) # new

# blueprints
API_URL_PREFIX = "/api/v1"
from api.views import books_bp

app.register_blueprint(books_bp, url_prefix=API_URL_PREFIX)

return app

We should import db in the api/__init__.py too!

# api/__init__.py
from api.app import create_app, db

Now it’s time to create our models. Open the models.py

# internals
from api import db

class Book(db.Document):
name = db.StringField(max_length=64)
author = db.StringField(max_length=32)
published_year = db.IntField(min=610)

def __str__(self) -> str:
return self.name

After creating the model let’s play on the flask shell. Open up the terminal

$ flask shell
# Python 3.11.0 (v3.11.0:deaf509e8f, Oct 24 2022, 14:43:23) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
# App: api.app
# Instance: /<my-path>/Flask_MongoDB_Docker/instance
>>> from api.models import *
>>> book = Book(name="Python Flask", author="Adnan Kaya", published_year=2023)
>>> book.save()
<Book: Python Flask>

Here we go! We have just created our first record on the database. Let’s check it out on the mongo shell

test> show dbs
admin 100.00 KiB
config 92.00 KiB
local 72.00 KiB
mydb 8.00 KiB # our database is created

# now select our db which is mydb
test> use mydb
switched to db mydb

# show collections(like tables)
mydb> show collections
book

# fetch the book records
mydb> db.book.find()
[
{
_id: ObjectId("638fb5df4653579e2ea4367c"),
name: 'Python Flask',
author: 'Adnan Kaya',
published_year: 2023
}
]

# GREAT!!!!

We added 2 more records. Now let’s edit api/views.py to get data from database via our api.

# api/views.py
from flask import Blueprint
# internals
from .models import Book

books_bp = Blueprint("books", __name__)


@books_bp.route("/books/", methods=["GET"])
def books():
book_list = Book.objects.all()

return book_list

Now make a GET request to the http://localhost:5000/api/v1/books/ response status 200 but we do not have any response data and also we have got error in the terminal

127.0.0.1 - - [07/Dec/2022 00:53:38] "GET /api/v1/books/ HTTP/1.1" 200 -
Error on request:
Traceback (most recent call last):
File "/<mypath>/Flask_MongoDB_Docker/env311/lib/python3.11/site-packages/werkzeug/serving.py", line 335, in run_wsgi
execute(self.server.app)
File "/<mypath>/Flask_MongoDB_Docker/env311/lib/python3.11/site-packages/werkzeug/serving.py", line 325, in execute
write(data)
File "/<mypath>/Flask_MongoDB_Docker/env311/lib/python3.11/site-packages/werkzeug/serving.py", line 293, in write
assert isinstance(data, bytes), "applications must write bytes"
AssertionError: applications must write bytes
AssertionError: applications must write bytes

The type of book_list in the views.py is

<class 'flask_mongoengine.BaseQuerySet'>

To overcome this problem we should use flask.jsonify( ) . Open api/views.py and import jsonify and return book_list by passing to jsonify

from flask import Blueprint, jsonify # new
# internals
from .models import Book

books_bp = Blueprint("books", __name__)


@books_bp.route("/books/", methods=["GET"])
def books():
book_list = Book.objects.all()

return jsonify(book_list) # new

Now make a GET request to the http://localhost:5000/api/v1/books/ and you will get the following result

[
{
"_id": {
"$oid": "638fb5df4653579e2ea4367c"
},
"author": "Adnan Kaya",
"name": "Python Flask",
"published_year": 2023
},
{
"_id": {
"$oid": "638fb80a4653579e2ea4367d"
},
"author": "Adnan Kaya",
"name": "Python Django",
"published_year": 2025
},
{
"_id": {
"$oid": "638fb83b4653579e2ea4367e"
},
"author": "Adnan Kaya",
"name": "Python Tkinter",
"published_year": 2026
}
]

_id and $oid (object id) is generated automatically.

It works fine :) . You can access the github repo in here.

You can read previous tutorial here.

If you want to read some information about me and contact visit my github page: https://adnankaya.github.io/

--

--