FastAPI & MongoDB CRUD: A Simple Example
FastAPI & MongoDB CRUD: A Simple Example
Hey everyone! Today, we’re diving deep into building a FastAPI MongoDB CRUD application. If you’re new to either FastAPI or MongoDB, or just looking for a straightforward example, you’ve come to the right place, guys. We’re going to walk through creating a basic Create, Read, Update, and Delete (CRUD) system using these awesome technologies. This tutorial is all about making it easy to understand, so we’ll break down each step. Get ready to build something cool!
Table of Contents
Setting the Stage: Why FastAPI and MongoDB?
Before we jump into the code, let’s chat for a sec about why we’re using FastAPI and MongoDB for our CRUD example. FastAPI is a modern, fast (hence the name!), web framework for building APIs with Python 3.7+ based on standard Python type hints. It’s super performant, easy to learn, and has automatic interactive API documentation. It’s a total game-changer for API development, seriously. On the other hand, MongoDB is a popular NoSQL document database. It’s flexible, scalable, and great for handling large amounts of unstructured or semi-structured data. Unlike traditional relational databases, MongoDB stores data in JSON-like documents, making it a natural fit for web applications, especially those built with Python where dictionaries and JSON are commonplace. Combining FastAPI’s speed and ease of use with MongoDB’s flexibility makes for a really powerful backend solution. This combo is perfect for rapid development, prototyping, and building scalable applications. So, stick around, and let’s get this awesome FastAPI MongoDB CRUD project rolling!
Prerequisites: What You’ll Need
Alright, before we get our hands dirty with the
FastAPI MongoDB CRUD
code, let’s make sure you have the essentials. You’ll need Python installed on your machine, obviously. If you don’t have it, head over to
python.org
and grab the latest version. You’ll also need
pip
, the Python package installer, which usually comes bundled with Python. Next up, we need MongoDB. You can either install MongoDB Community Server on your local machine (check out the
official MongoDB installation guide
) or use a cloud-based service like MongoDB Atlas, which offers a free tier that’s perfect for development and learning. I personally love MongoDB Atlas because it’s super easy to set up and you don’t have to worry about local installations. Finally, you’ll need a way to interact with your API during development. Tools like
curl
, Postman, or Insomnia are your best friends here. We’ll be using these to test our CRUD operations. Make sure you’re comfortable with the command line, as we’ll be running some commands to install packages and start our server. That’s pretty much it! With these tools in place, we’re all set to build our
FastAPI MongoDB CRUD
example. Let’s do this!
Step 1: Project Setup and Dependencies
Okay, team, let’s get this
FastAPI MongoDB CRUD
party started with the project setup. First things first, create a new directory for your project. You can name it whatever you like, maybe
fastapi_mongo_crud
. Navigate into that directory using your terminal. Now, it’s a good practice to set up a virtual environment. This keeps your project dependencies isolated from your system’s Python installation. You can create one by running:
python -m venv venv
And then activate it. On Windows, it’s
.\venv\Scripts\activate
. On macOS and Linux, it’s
source venv/bin/activate
. You should see
(venv)
at the beginning of your terminal prompt, indicating it’s active. Cool, right? Now, let’s install the necessary libraries. We’ll need
fastapi
for our web framework,
uvicorn
as the ASGI server to run FastAPI, and
motor
which is an asynchronous MongoDB driver for Python. So, type this into your terminal:
pip install fastapi uvicorn motor
Once those are installed, we’re almost ready to start coding. I also recommend installing
python-dotenv
to manage your environment variables, especially for database connection strings. Let’s add that:
pip install python-dotenv
For managing your MongoDB connection string, create a file named
.env
in your project’s root directory. Add your MongoDB connection string to it. If you’re using MongoDB Atlas, it will look something like this (replace the placeholders with your actual credentials):
MONGO_DETAILS="mongodb://user:password@host:port/database?retryWrites=true&w=majority"
Remember to keep this
.env
file private and add it to your
.gitignore
if you’re using Git. This is super important for security, guys. With our dependencies installed and environment variables set up, we’ve successfully laid the groundwork for our
FastAPI MongoDB CRUD
application. High five!
Step 2: Connecting to MongoDB
Now that our environment is all set up, let’s tackle the crucial part: connecting to
MongoDB
from our
FastAPI
application. We’ll use the
motor
library for this, as it’s designed to work seamlessly with asynchronous frameworks like FastAPI. Create a new Python file, let’s call it
database.py
. This file will house our database connection logic.
Inside
database.py
, we’ll import necessary modules. We need
motor.motor_asyncio
for the asynchronous MongoDB client and
os
to access our environment variables. We’ll also import
load_dotenv
from
dotenv
to load our
.env
file.
import os
from dotenv import load_dotenv
from motor.motor_asyncio import AsyncIOMotorClient
load_dotenv()
MONGO_DETAILS = os.getenv("MONGO_DETAILS")
def get_database():
client = AsyncIOMotorClient(MONGO_DETAILS)
return client.mydatabase # Replace 'mydatabase' with your actual database name
async def close_database_connection(client: AsyncIOMotorClient):
client.close()
In this code snippet,
load_dotenv()
loads the variables from our
.env
file.
os.getenv("MONGO_DETAILS")
retrieves the MongoDB connection string we stored earlier. Then,
AsyncIOMotorClient(MONGO_DETAILS)
creates an asynchronous client instance. Finally,
client.mydatabase
returns a reference to your specific database.
Make sure to replace
mydatabase
with the actual name of your database
that you want to use for this project. We also added a
close_database_connection
function, which is good practice for gracefully closing the connection when the application shuts down, although FastAPI with Uvicorn often handles this automatically. This function is key for managing resources efficiently. This step is fundamental for any
FastAPI MongoDB CRUD
operation, ensuring we have a reliable connection to our data store.
Step 3: Defining the Data Model (Pydantic Model)
Alright folks, the next step in our
FastAPI MongoDB CRUD
journey is defining our data model. FastAPI heavily relies on Pydantic for data validation and serialization. This means we need to create a Pydantic model that represents the structure of the data we’ll be storing in MongoDB. Let’s create a new file named
models.py
.
For this example, let’s imagine we’re building a simple task management application. So, our model will represent a ‘Task’. A task might have an ID, a title, a description, and a status (e.g., ‘pending’, ‘completed’).
from pydantic import BaseModel, Field
from typing import Optional
class PyObjectId(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if not isinstance(v, str):
raise TypeError('string required')
return str(v)
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="string")
class TaskBase(BaseModel):
title: str = Field(..., example="Buy groceries")
description: Optional[str] = Field(None, example="Milk, Eggs, Bread")
completed: bool = False
class Task(TaskBase):
id: Optional[PyObjectId] = Field(alias="_id")
class TaskInDB(TaskBase):
pass
Let’s break this down.
PyObjectId
is a custom Pydantic type to handle MongoDB’s
_id
field, which is an ObjectId. We need this because Pydantic usually expects standard Python types.
TaskBase
is our base model containing the fields that will always be present when creating a task:
title
,
description
, and
completed
. We’ve added
Optional
for
description
to make it not required and provided some example values for better documentation.
Task
inherits from
TaskBase
and adds the
id
field, which maps to MongoDB’s
_id
. The
alias="_id"
tells Pydantic to expect
_id
from MongoDB and map it to the
id
field in our Python object.
TaskInDB
is similar to
TaskBase
but could be used for returning data from the database, potentially including generated IDs if we were to add them directly here. For our
FastAPI MongoDB CRUD
operations,
Task
will be crucial for representing tasks retrieved from the database, and
TaskBase
for creating new ones.
Step 4: Implementing CRUD Operations
Now for the main event, guys! We’re going to implement the
FastAPI MongoDB CRUD
operations. Create a new file named
main.py
. This is where our FastAPI application will live.
First, let’s import everything we need: FastAPI itself, our Pydantic models from
models.py
, and our database connection function from
database.py
. We’ll also need
HTTPException
from
fastapi
for handling errors.
from fastapi import FastAPI, HTTPException, Body
from fastapi.responses import JSONResponse
from typing import List
from database import get_database
from models import Task, TaskBase, PyObjectId
app = FastAPI()
db = get_database()
tasks_collection = db.tasks # Assuming your collection is named 'tasks'
Let’s start with the Create operation. This endpoint will allow us to add a new task.
@app.post('/tasks/', response_model=Task)
async def create_task(task: TaskBase = Body(...)):
task_dict = task.dict()
new_task = await tasks_collection.insert_one(task_dict)
created_task = await tasks_collection.find_one({'_id': new_task.inserted_id})
return Task(**created_task)
Here,
create_task
accepts a
TaskBase
model. We convert it to a dictionary using
task.dict()
, insert it into our
tasks_collection
, and then retrieve the newly created task, including its generated
_id
, to return it as a
Task
model. Pretty neat, huh?
Next, the Read operation. We’ll implement two endpoints: one to get all tasks and another to get a single task by its ID.
@app.get('/tasks/', response_model=List[Task])
async def get_all_tasks():
tasks = await tasks_collection.find().to_list(length=100) # Limit results
return tasks
@app.get('/tasks/{task_id}', response_model=Task)
async def get_task(task_id: str):
try:
# Attempt to convert task_id to ObjectId
obj_id = PyObjectId(task_id)
except Exception: # Catch any validation errors from PyObjectId
raise HTTPException(status_code=400, detail=f"Invalid task ID format: {task_id}")
task = await tasks_collection.find_one({'_id': obj_id})
if task:
return Task(**task)
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
get_all_tasks
retrieves up to 100 tasks.
get_task
takes a
task_id
, converts it to a
PyObjectId
for safe querying, and returns the task if found, or a 404 error otherwise. The
PyObjectId
conversion is important for robust error handling.
Now, the Update operation. This endpoint will modify an existing task.
@app.put('/tasks/{task_id}', response_model=Task)
async def update_task(task_id: str, task_update: TaskBase = Body(...)):
try:
obj_id = PyObjectId(task_id)
except Exception:
raise HTTPException(status_code=400, detail=f"Invalid task ID format: {task_id}")
update_data = task_update.dict(exclude_unset=True)
update_result = await tasks_collection.update_one({'_id': obj_id}, {'$set': update_data})
if update_result.modified_count == 1:
updated_task = await tasks_collection.find_one({'_id': obj_id})
return Task(**updated_task)
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
update_task
takes the
task_id
and a
TaskBase
model with the new data.
task_update.dict(exclude_unset=True)
ensures we only update fields that are actually provided. We use
update_one
with the
$set
operator. If the update was successful (modified count is 1), we fetch and return the updated task; otherwise, we return a 404.
Finally, the Delete operation. This will remove a task.
@app.delete('/tasks/{task_id}', response_model=dict)
async def delete_task(task_id: str):
try:
obj_id = PyObjectId(task_id)
except Exception:
raise HTTPException(status_code=400, detail=f"Invalid task ID format: {task_id}")
delete_result = await tasks_collection.delete_one({'_id': obj_id})
if delete_result.deleted_count == 1:
return {"message": f"Task {task_id} deleted successfully"}
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
delete_task
uses the
task_id
to find and delete the task. If
deleted_count
is 1, we return a success message; otherwise, a 404. These endpoints cover the core
FastAPI MongoDB CRUD
functionality. You’re building a solid API right now!
Step 5: Running the Application
We’ve written the code, and now it’s time to see our FastAPI MongoDB CRUD application in action! Ensure you are in your project’s root directory in the terminal, and your virtual environment is activated. Run the following command:
uvicorn main:app --reload
This command starts the Uvicorn server.
main:app
tells Uvicorn to look for the
app
instance in your
main.py
file. The
--reload
flag is super handy during development because it automatically restarts the server whenever you make changes to your code. You should see output indicating that the server is running, typically on
http://127.0.0.1:8000
.
Now, the magic part: FastAPI automatically generates interactive API documentation. Open your web browser and navigate to
http://127.0.0.1:8000/docs
. You’ll be greeted by the Swagger UI, which lets you explore and test all your API endpoints directly from the browser. You can see the
POST /tasks/
endpoint to create a new task,
GET /tasks/
to list all tasks,
GET /tasks/{task_id}
to retrieve a specific task,
PUT /tasks/{task_id}
to update a task, and
DELETE /tasks/{task_id}
to delete one. You can expand each endpoint, fill in the required parameters, and click ‘Execute’ to send requests to your server and see the responses. This interactive documentation is one of the killer features of FastAPI and makes testing your
FastAPI MongoDB CRUD
endpoints incredibly easy. Alternatively, you can also check out
http://127.0.0.1:8000/redoc
for a different documentation style. Go ahead, try creating, reading, updating, and deleting some tasks! This is where all your hard work pays off, guys!
Conclusion and Next Steps
And there you have it, folks! You’ve successfully built a
FastAPI MongoDB CRUD
application. We covered everything from setting up your project and dependencies, connecting to MongoDB using
motor
, defining Pydantic models, implementing the core Create, Read, Update, and Delete operations, and finally running and testing your API using Uvicorn and FastAPI’s interactive documentation. This example provides a solid foundation for building more complex applications. Remember, practice is key, so try modifying this example, maybe add more fields to the Task model, implement more robust error handling, or explore authentication and authorization. You could also look into using MongoDB’s aggregation framework for more complex queries or integrate this with a frontend framework like React or Vue.js. The possibilities are endless! Keep experimenting, keep building, and happy coding!