Rest API development with Python, Flask, MongoDB, Docker | Part 1
In this tutorial series we are going to develop a rest api with python, flask, mongodb and docker.
Contents
- Installation and Project Structure
- Running the app and adding first view and endpoint
- Working with Flask, Mongoengine and MongoDB
Requirements
- Download python3.11
- Docker version 20.10.21
- Flask==2.2.2
- flask-mongoengine==1.0.0
- flask-marshmallow==0.14.0
- 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/