Integrating FastAPI and MongoDB
In this latest installment of FastAPI tutorials, we will focus on integrating FastAPI with a MongoDB database backend.
MongoDB is a document oriented NoSQL database that stores JSON documents. As both MongoDB and FastAPI work natively with JSON, they make a good pair.
Getting Started with MongoDB
To get started, you first need to install the community edition of MongoDB. MongoDB provides complete instructions for all platforms, including Mac OS X, Windows and Linux. Next, it helps to familiarize yourself with the Mongo Shell. You should know enough of the basics to use the Mongo Shell for creating new records and then retrieving them.
It’s also quite helpful to use a GUI for your MongoDB instance. This enables you to browse databases, collections and records, which can quite helpful when debugging your FastAPI application. I recommend Robo 3T — it’s free and easy to use.
Getting Started with PyMongo
PyMongo is the official Python database driver for MongoDB.
To install PyMongo, run:
pip install pymongo
You can then connect to your MongoDB database server via the MongoClient
. For example:
from pymongo import MongoClient
client = MongoClient()
With no arguments to MongoClient()
, you will connect to the default, local MongoDB server. Once the connection is established, you can drill down to a specific database or a specific collection within the database. For example, let's imagine that we want to store messages for our Slack clone (see Up and Running with FastAPI for a description of the Slack clone). In this case, we will need a slack
database, and a collection called messages
. We can use the following code to connect:
db = client["slack"]
msg_collection = db["messages"]
The code above should always work. For example, if the database and collection already exist, you will now have the correct references. If either does not yet exist within the database, MongoDB will automatically create them. Note however that MongoDB works in lazy mode, and will not create the database or collection until you actually insert your first document.
Inserting Records
To insert new records, you specify your record as a Python dict
, and then use the insert_one()
or insert_many()
methods. For example, let's define a new message for our Slack code:
# Create a message dict
message = {
"channel": "dev",
"author": "cerami",
"text": "Hello, world!"
}
We can then use the insert_one()
method:
result = msg_collection.insert_one(message)
print(result.inserted_id)
If successful, MongoDB will automatically create an ID for your new document, and the result object will include the newly generated inserted_id
. You should then receive a notification like this:
Inserted: 604793d07fca43827e384801
Retrieving Records
Retrieving records with PyMongo is just as easy. For example, we can retrieve all the document in msg_collection
:
import pprint
pp = pprint.PrettyPrinter(indent=4)
for doc in msg_collection.find():
pp.pprint(doc)
Here I am using the find()
argument with no arguments, and also using pprint
to pretty print the returned document. You can also pass dict
arguments to find()
in order to retrieve matching records. For example, you can retrieve all messages within a specific channel:
record_list = msg_collection.find({"channel": "dev"}):
or, all messages written by a specific author.
record_list = msg_collection.find({"author": "cerami"})
Python Unpack Operator
We now know enough PyMongo to start integrating with FastAPI. But, there is one more useful concept we need under our belt — the Python **
unpacking operator!
To understand how the unpacking operator works, let’s consider a simple Pydantic class that stores information about people.
New to Pydantic? Check out FastAPI: Data Models.
from pydantic import BaseModelclass Person(BaseModel):
first: str
last: str
zip_code: str def __str__(self):
return "%s %s: %s" % (self.first, self.last, self.zip_code)
Here we have a Person
class with three attributes, all defined as strings. I have also added a __str__
method for easy printing.
As with our previous Pydantic examples, Person
extends BaseModel
, and the constructor of BaseModel
is defined to take any number of arguments. We can therefore instantiate a Person
object like so:
person = Person(first="Bruce", last="Wayne", zip_code="10021")
Pydantic is clever enough to take all our arguments, and assign them to the correct attributes. If we then call print(person)
, we get:
Bruce Wayne: 10021
Now, let’s suppose we have a dict
object like so:
person_dict = {
"first": "Bruce",
"last": "Wayne",
"zip_code": "10021"
}
Further, we now want to use this information to create a new Person
object. One option is to unpack everything ourselves. For example:
person = Person(
first=person_dict["first"],
last=person_dict["last"],
zip_code=person_dict["zip_code"])
However, this can make for a whole lot of code. A much more compact option is to use the **
Python unpacking operator. The unpacking operator will automatically unpack all the members of your dictionary, and pass them along for you! This results in much cleaner and flexible code:
person = Person(**person_dict)
The unpacking operator is particularly convenient for FastAPI and MongoDB, because FastAPI works with Pydantic models, and MongoDB works with dict
objects. The **
Python unpacking operator provides the necessary glue to easily convert dict
objects from MongoDB to Pydantic models needed by FastAPI. We will see additional examples in the next section.
Fast API and MongoDB
With the basics of PyMongo and the Python **
unpacking operator under our belt, we are now ready to start building our FastAPI application. Continuing with the theme of our Slack clone, we will keep the same API endpoints, but now add a MongoDB backend.
Here is the complete code:
There are a few important elements to consider in the code above. Let’s take a closer look:
- We have our standard
Message
class that extends the PydanticBaseModel
. - The
get_message_collection()
method connects to the MongoDB database, and retrieves themessages
collection from theslack
database. - The
get_channels()
method leverages the PyMongodistinct()
method to retrieve the list of distinct channels in themessage
collection. - The
get_messages()
method leverages the**
unpacking operator to easily convert from MongoDBdict
objects to Pydantic-basedMessage
objects.
Note: As I have since discovered, this is not the most performant option for managing your connections to MongoDB. For a better option, see my blog post on Benchmarking FastAPI and MongoDB Options.
To run the code above, first make sure that your MongoDB server is running. Then, as with our previous FastAPI examples, you can run the application via uvicorn:
uvicorn slack:app --reload
And, with that, you are up and running with FastAPI and MongoDB!