In this guide, we will walk through the step-by-step process of building a FastAPI application complete with MongoDB models for data handling. The framework FastAPI, known for its high performance and ease of use, combined with MongoDB's NoSQL flexibility, allows the creation of powerful and scalable APIs. By following this guide, you will learn how to establish a connection to MongoDB, craft your models with Pydantic, implement essential CRUD endpoints, and deploy your application using an ASGI server like Uvicorn.
Before starting, ensure you are running Python 3.7 or later. FastAPI and its dependencies are designed to work on modern versions of Python. It is recommended to work within a virtual environment to keep your project dependencies isolated. You can use either venv or any other virtual environment tool of your choice.
Create your virtual environment and activate it using the commands:
# Create a virtual environment
python3 -m venv env
# Activate the virtual environment on Unix or MacOS
source env/bin/activate
# Activate the virtual environment on Windows
env\Scripts\activate
With the environment activated, install the essential libraries with pip. You will need FastAPI, Uvicorn for running the application, and Motor to manage asynchronous MongoDB operations:
pip install fastapi uvicorn motor
Organize your project to separate concerns and maintain a clear structure. A suggested project layout may look like this:
File/Folder | Description |
---|---|
main.py | Main application file where the FastAPI instance and routes are defined. |
database.py | Handles the MongoDB connection by initializing a Motor client. |
models.py | Contains Pydantic models used for data validation and serialization. |
requirements.txt | Lists the dependencies for the project. |
This modular approach helps in maintaining clean code and allows for easier expansion later on.
In order to interact with MongoDB asynchronously, create a module named "database.py" that handles the connection. This module will initialize the Motor client and provide access to your MongoDB database.
Add the following code to manage your MongoDB connection:
# database.py
from motor.motor_asyncio import AsyncIOMotorClient
# Replace the connection string if you are using MongoDB Atlas or a different host.
MONGO_DETAILS = "mongodb://localhost:27017"
# Connect to the MongoDB server
client = AsyncIOMotorClient(MONGO_DETAILS)
# Access your database by name
database = client.mydatabase
To facilitate data validation, conversion, and serialization, use Pydantic models. These models ensure that the data being received and returned by your API conforms to the specified schema. Create a "models.py" file where you will define your MongoDB models.
For this example, we will define a basic "ItemModel" and an "UpdateItemModel" to handle document operations:
# models.py
from pydantic import BaseModel
from typing import Optional
class ItemModel(BaseModel):
id: Optional[str]
name: str
description: str
class UpdateItemModel(BaseModel):
name: Optional[str]
description: Optional[str]
The ItemModel
will be used for creating and representing items, while the UpdateItemModel
will help update specific fields during an update operation.
With your database connection and data models ready, the next step is to build the API endpoints to perform CRUD operations (Create, Read, Update, Delete) on your MongoDB documents. All endpoints will be defined in your main.py
file.
Below is a comprehensive example of how to create, read, update, and delete items within your FastAPI app:
# main.py
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from bson import ObjectId
from database import database
from models import ItemModel, UpdateItemModel
app = FastAPI()
COLLECTION_NAME = "items"
# Create Item
@app.post("/items/", response_model=ItemModel)
async def create_item(item: ItemModel):
# Convert item to a dictionary and prepare for insertion
item_dict = item.dict()
# Insert item into MongoDB and retrieve its newly generated _id
result = await database[COLLECTION_NAME].insert_one(item_dict)
# Convert ObjectId to string for the response
item_dict["id"] = str(result.inserted_id)
return JSONResponse(content=item_dict, status_code=201)
# Read All Items
@app.get("/items/", response_model=list)
async def read_all_items():
items = await database[COLLECTION_NAME].find().to_list(1000)
for item in items:
item["id"] = str(item["_id"])
return JSONResponse(content=items, status_code=200)
# Read a Single Item
@app.get("/items/{item_id}", response_model=ItemModel)
async def read_item(item_id: str):
item = await database[COLLECTION_NAME].find_one({"_id": ObjectId(item_id)})
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
item["id"] = str(item["_id"])
return JSONResponse(content=item, status_code=200)
# Update Item
@app.put("/items/{item_id}", response_model=dict)
async def update_item(item_id: str, item: UpdateItemModel):
item_data = {key: value for key, value in item.dict().items() if value is not None}
result = await database[COLLECTION_NAME].update_one(
{"_id": ObjectId(item_id)},
{"$set": item_data}
)
if result.modified_count == 0:
raise HTTPException(status_code=404, detail="Item not found or no data updated")
return JSONResponse(content={"message": "Item updated"}, status_code=200)
# Delete Item
@app.delete("/items/{item_id}", response_model=dict)
async def delete_item(item_id: str):
result = await database[COLLECTION_NAME].delete_one({"_id": ObjectId(item_id)})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="Item not found")
return JSONResponse(content={"message": "Item deleted"}, status_code=200)
This code sample implements all critical CRUD operations:
Once your code is in place, run your application using Uvicorn. Execute the following command in your terminal:
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
The --reload
flag allows the server to automatically reload whenever changes in the source code are detected. With the server running, you can access your API documentation, auto-generated by FastAPI, by navigating to:
http://127.0.0.1:8000/docs
Pydantic models enforce data validation automatically. However, it is important to add specific error handling routines when interacting with your database. Using FastAPI's exception handlers and HTTPException can improve the reliability of your API. Make sure to return meaningful error messages to help consumers of your API troubleshoot issues.
MongoDB’s _id
field is of type ObjectId. In your application, convert these ObjectIds to their string counterparts before including them in JSON responses. When receiving data, convert strings back to ObjectIds as needed. This ensures consistency between your API and client-side applications.
Because FastAPI is asynchronous by design, integrating it with an async MongoDB driver like Motor boosts performance. For applications expecting high traffic, consider the following:
Endpoint | HTTP Method | Action |
---|---|---|
/items/ | POST | Create a new item |
/items/ | GET | Retrieve all items |
/items/{item_id} | GET | Retrieve a specific item |
/items/{item_id} | PUT | Update an existing item |
/items/{item_id} | DELETE | Delete a specific item |