Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
MongoDB
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
MongoDBchevron-right

8 Best Practices for Building FastAPI and MongoDB Applications

Mark Smith8 min read • Published Apr 23, 2024 • Updated Apr 23, 2024
FastApiMongoDBPython
SNIPPET
Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
FastAPI is a modern, high-performance web framework for building APIs with Python 3.8 or later, based on type hints. Its design focuses on quick coding and error reduction, thanks to automatic data model validation and less boilerplate code. FastAPI’s support for asynchronous programming ensures APIs are efficient and scalable, while built-in documentation features like Swagger UI and ReDoc provide interactive API exploration tools.
FastAPI seamlessly integrates with MongoDB through the Motor library, enabling asynchronous database interactions. This combination supports scalable applications by enhancing both the speed and flexibility of data handling with MongoDB. FastAPI and MongoDB together are ideal for creating applications that manage potentially large amounts of complex and diverse data efficiently. MongoDB is a proud sponsor of the FastAPI project, so you can tell it's a great choice for building applications with MongoDB.
All the techniques described in this article are available on GitHub — check out the source code! With that out of the way, now we can begin…
FastAPI is particularly suitable for building RESTful APIs, where requests for data and updates to the database are made using HTTP requests, usually with JSON payloads. But the framework is equally excellent as a back end for HTML websites or even full single-page applications (SPAs) where the majority of requests are made via JavaScript. (We call this the FARM stack — FastAPI, React, MongoDB — but you can swap in any front-end component framework that you like.) It's particularly flexible with regard to both the database back-end and the template language used to render HTML.

Use the right driver!

There are actually two Python drivers for MongoDB — PyMongo and Motor — but only one of them is suitable for use with FastAPI. Because FastAPI is built on top of ASGI and asyncio, you need to use Motor, which is compatible with asyncio. PyMongo is only for synchronous applications. Fortunately, just like PyMongo, Motor is developed and fully supported by MongoDB, so you can rely on it in production, just like you would with PyMongo.
You can install it by running the following command in your terminal (I recommend configuring a Python virtual environment first!):
1pip install motor[srv]
The srv extra includes some extra dependencies that are necessary for connecting with MongoDB Atlas connection strings.
Once installed, you'll need to use the AsyncIOMotorClient in the motor.motor_asyncio package.
1from fastapi import FastAPI
2from motor.motor_asyncio import AsyncIOMotorClient
3
4app = FastAPI()
5
6# Load the MongoDB connection string from the environment variable MONGODB_URI
7CONNECTION_STRING = os.environ['MONGODB_URI']
8
9# Create a MongoDB client
10client = AsyncIOMotorClient(CONNECTION_STRING)
Note that the connection string is not stored in the code! Which leads me to…

Keep your secrets safe

It's very easy to accidentally commit secret credentials in your code and push them to relatively insecure places like shared Git repositories. I recommend making it a habit to never put any secret in your code.
When working on code, I keep my secrets in a file called .envrc — the contents get loaded into environment variables by a tool called direnv. Other tools for keeping sensitive credentials out of your code include envdir, a library like python-dotenv, and there are various tools like Honcho and Foreman. You should use whichever tool makes the most sense to you. Whether the file that keeps your secrets is called .env or .envrc or something else, you should add that filename to your global gitignore file so that it never gets added to any repository.
In production, you should use a KMS (key management system) such as Vault, or perhaps the cloud-native KMS of whichever cloud you may be using to host your application. Some people even use a KMS to manage their secrets in development.

Initialize your database connection correctly

Although I initialized my database connection in the code above at the top level of a small FastAPI application, it's better practice to gracefully initialize and close your client connection by responding to startup and shutdown events in your FastAPI application. You should also attach your client to FastAPI's app object to make it available to your path operation functions wherever they are in your codebase. (Other frameworks sometimes refer to these as “routes” or “endpoints.” FastAPI calls them “path operations.”) If you rely on a global variable instead, you need to worry about importing it everywhere it's needed, which can be messy.
The snippet of code below shows how to respond to your application starting up and shutting down, and how to handle the client in response to each of these events:
1from contextlib import asynccontextmanager
2from logging import info @asynccontextmanager
3async def db_lifespan(app: FastAPI):
4 # Startup
5 app.mongodb_client = AsyncIOMotorClient(CONNECTION_STRING)
6 app.database = app.mongodb_client.get_default_database()
7 ping_response = await app.database.command("ping")
8 if int(ping_response["ok"]) != 1:
9 raise Exception("Problem connecting to database cluster.")
10 else:
11 info("Connected to database cluster.")
12
13 yield
14
15 # Shutdown
16 app.mongodb_client.close()
17
18
19app: FastAPI = FastAPI(lifespan=db_lifespan)

Consider using a Pydantic ODM

An ODM, or object-document mapper, is a library that converts between documents and objects in your code. It's largely analogous to an ORM in the world of RDBMS databases. Using an ODM is a complex topic, and sometimes they can obscure important things, such as the way data is stored and updated in the database, or even some advanced MongoDB features that you may want to take advantage of. Whichever ODM you choose, you should vet it highly to make sure that it's going to do what you want and grow with you.
If you're choosing an ODM for your FastAPI application, definitely consider using a Pydantic-based ODM, such as ODMantic or Beanie. The reason you should prefer one of these libraries is that FastAPI is built with tight integration to Pydantic. This means that if your path operations return a Pydantic object, the schema will automatically be documented using OpenAPI (which used to be called Swagger), and FastAPI also provides nice API documentation under the path "/docs". As well as documenting your interface, it also provides validation of the data you're returning.
1class Profile(Document):
2 """
3 A profile for a single user as a Beanie Document.
4
5 Contains some useful information about a person.
6 """
7
8 # Use a string for _id, instead of ObjectID:
9 id: Optional[str] = Field(default=None, description="MongoDB document ObjectID")
10 username: str
11 birthdate: datetime
12 website: List[str]
13
14 class Settings:
15 # The name of the collection to store these objects.
16 name = "profiles"
17
18# A sample path operation to get a Profile:
19@app.get("/profiles/{profile_id}")
20async def get_profile(profile_id: str) -> Profile:
21 """
22 Look up a single profile by ID.
23 """
24 # This API endpoint demonstrates using Motor directly to look up a single
25 # profile by ID.
26 profile = await Profile.get(profile_id)
27 if profile is not None:
28 return profile
29 else:
30 raise HTTPException(
31 status_code=404, detail=f"No profile with id '{profile_id}'"
32 )
The profile object above is automatically documented at the "/docs" path:
A screenshot of the auto-generated documentation

You can use Motor directly

If you feel that working directly with the Python MongoDB driver, Motor, makes more sense to you, I can tell you that it works very well for many large, complex MongoDB applications in production. If you still want the benefits of automated API documentation, you can document your schema in your code so that it will be picked up by FastAPI.

Remember that some BSON has more types than JSON

As many FastAPI applications include endpoints that provide JSON data that is retrieved from MongoDB, it's important to remember that certain types you may store in your database, especially the ObjectID and Binary types, don't exist in JSON. FastAPI fortunately handles dates and datetimes for you, by encoding them as formatted strings.
There are a few different ways to handle ObjectID mappings. The first is to avoid them completely by using a JSON-compatible type (such as a string) for _id values. In many cases, this isn't practical though, because you already have data, or just because ObjectID is the most appropriate type for your primary key. In this case, you'll probably want to convert ObjectIDs to a string representation when converting to JSON, and do the reverse with data that's being submitted to your application.
If you're using Beanie, it automatically assumes that the type of your _id is an ObjectID, and so will set the field type to PydanticObjectId, which will automatically handle this serialization mapping for you. You won't even need to declare the id in your model!

Define Pydantic types for your path operation responses

If you specify the response type of your path operations, FastAPI will validate the responses you provide, and also filter any fields that aren't defined on the response type.
Because ODMantic and Beanie use Pydantic under the hood, you can return those objects directly. Here's an example using Beanie:
1@app.get("/people/{profile_id}")
2async def read_item(profile_id: str) -> Profile:
3 """ Use Beanie to look up a Profile. """
4 profile = await Profile.get(profile_id)
5 return profile
If you're using Motor, you can still get the benefits of documentation, conversion, validation, and filtering by returning document data, but by providing the Pydantic model to the decorator:
1@app.get(
2 "/people/{profile_id}",
3 response_model=Profile,
4)
5async def read_item(profile_id: str) -> Mapping[str, Any]:
6 # This API endpoint demonstrates using Motor directly to look up a single
7 # profile by ID.
8 #
9 # It uses response_model (above) to tell FastAPI the schema of the data
10 # being returned, but it returns a dict directly, so that conversion and
11 # validation is done by FastAPI, meaning you don't have to copy values
12 # manually into a Profile before returning it.
13 profile = await app.profiles.find_one({"_id": profile_id})
14 if profile is not None:
15 return profile

Remember to model your data appropriately

A common mistake people make when building RESTful API servers on top of MongoDB is to store the objects of their API interface in exactly the same way in their MongoDB database. This can work very well in simple cases, especially if the application is a relatively straightforward CRUD API.
In many cases, however, you'll want to think about how to best model your data for efficient updates and retrieval and aid in maintaining referential integrity and reasonably sized indexes. This is a topic all of its own, so definitely check out the series of design pattern articles on the MongoDB website, and maybe consider doing the free Advanced Schema Design Patterns online course at MongoDB University. (There are lots of amazing free courses on many different topics at MongoDB University.)
If you're working with a different data model in your database than that in your application, you will need to map values retrieved from the database and values provided via requests to your API path operations. Separating your physical model from your business model has the benefit of allowing you to change your database schema without necessarily changing your API schema (and vice versa).
Even if you're not mapping data returned from the database (yet), providing a Pydantic class as the response_model for your path operation will convert, validate, document, and filter the fields of the BSON data you're returning, so it provides lots of value! Here's an example of using this technique in a FastAPI app:
1# A Pydantic class modelling the *response* schema.
2class Profile(BaseModel):
3 """
4 A profile for a single user.
5 """
6 id: Optional[str] = Field(
7 default=None, description="MongoDB document ObjectID", alias="_id"
8 )
9 username: str
10 residence: str
11 current_location: List[float]
12
13# A path operation that returns a Profile object as JSON:
14@app.get(
15 "/profiles/{profile_id}",
16 response_model=Profile, # This tells FastAPI that the returned object must match the Profile schema.
17)
18async def get_profile(profile_id: str) -> Mapping[str, Any]:
19 # Uses response_model (above) to tell FastAPI the schema of the data
20 # being returned, but it returns a dict directly, so that conversion and
21 # validation is done by FastAPI, meaning you don't have to copy values
22 # manually into a Profile before returning it.
23 profile = await app.profiles.find_one({"_id": profile_id})
24 if profile is not None:
25 return profile # Return BSON document (Mapping). Conversion etc will be done automatically.
26 else:
27 raise HTTPException(
28 status_code=404, detail=f"No profile with id '{profile_id}'"
29 )

Use the Full-Stack FastAPI & MongoDB Generator

My amazing colleagues have built an app generator to do a lot of these things for you and help get you up and running as quickly as possible with a production-quality, dockerized FastAPI, React, and MongoDB service, backed by tests and continuous integration. You can check it out at the Full-Stack FastAPI MongoDB GitHub Repository.
The homepage of an app built with the FastAPI Generator

Conclusion

Hopefully, this article has provided you with the knowledge to build a FastAPI application that will scale in both the amount of code you write, and the data you need to store. We're always looking for more tricks and tips for building applications with FastAPI and MongoDB, so if you have some suggestions, why not open a ticket on GitHub and we can have a chat?

Let us know what you're building!

We love to know what you're building with FastAPI or any other framework — whether it's a hobby project or an enterprise application that's going to change the world. Let us know what you're building at the MongoDB Community Forums. It's also a great place to stop by if you're having problems — someone on the forums can probably help you out!
Top Comments in Forums
Forum Commenter Avatar
Alessio_RuggiAlessio Ruggilast quarter

I’m commenting only to point out that the link to github doesn’t seem to work

See More on Forums

Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Article

Building a Flask and MongoDB App with Azure Container Apps


Apr 02, 2024 | 8 min read
Tutorial

Joining Collections in MongoDB with .NET Core and an Aggregation Pipeline


Feb 03, 2023 | 7 min read
Tutorial

Secure your API with Spring Data MongoDB and Microsoft EntraID


Apr 02, 2024 | 8 min read
News & Announcements

Improved Error Messages for Schema Validation in MongoDB 5.0


Jun 14, 2023 | 10 min read
Table of Contents