Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tutosrive/fastapi-CRUD-MongoDB/llms.txt

Use this file to discover all available pages before exploring further.

This project follows a clean, layered architecture that separates concerns across four distinct tiers. An incoming HTTP request is first received by a route handler, which delegates business logic to a controller. The controller validates data against Pydantic models, queries MongoDB via PyMongo, and passes raw documents through schema serializers before returning a well-typed response to the client. This separation keeps each layer focused and independently testable.

Directory Layout

fastapi-CRUD-MongoDB/
├── main.py
├── requirements.txt
└── app/
    ├── config/
    │   ├── db.py
    │   └── docs.py
    ├── controllers/
    │   ├── task.py
    │   └── user.py
    ├── models/
    │   ├── task.py
    │   └── user.py
    ├── routes/
    │   ├── home.py
    │   ├── task.py
    │   └── user.py
    └── schemas/
        ├── task.py
        └── user.py

Layer-by-Layer Breakdown

main.py is the top-level entry point for the FastAPI application. It creates the FastAPI instance, configures the app’s metadata (title, description, version, and OpenAPI tag groupings), and registers the three routers — home_router, user_router, and task_router — via app.include_router().
from fastapi import FastAPI

from app.routes.home import home_router
from app.routes.user import user_router
from app.routes.task import task_router
from app.config.docs import api_tags

app = FastAPI(
    title='FASTAPI + MongoDB',
    description='Learning FastAPI with MongoDB ...',
    version='2025.0.1.0',
    openapi_tags=api_tags
)

app.include_router(home_router)
app.include_router(user_router)
app.include_router(task_router)
The openapi_tags list (sourced from app/config/docs.py) controls how endpoint groups appear in the Swagger UI.
This module establishes the single shared PyMongo connection used throughout the application. Calling MongoClient() with no arguments connects to a MongoDB instance running on localhost:27017 — the default development setup.
from pymongo import MongoClient

mong_conn = MongoClient()
The mong_conn object is imported wherever a database operation is needed. Collections are accessed as mong_conn.local.user and mong_conn.local.task, both residing in MongoDB’s built-in local database.
docs.py defines the api_tags list that is passed to the FastAPI() constructor. Each entry provides a name and description that FastAPI uses to group and label endpoints in the auto-generated Swagger UI (/docs) and ReDoc (/redoc) interfaces.
api_tags = [
    {
        'name': 'User',
        'description': 'The user CRUD'
    },
    {
        'name': 'Task',
        'description': 'The tasks CRUD'
    }
]
Keeping tag metadata in a dedicated module avoids cluttering main.py and makes it easy to expand descriptions or add new resource groups.
The models/ directory contains Pydantic BaseModel classes that define the shape of data flowing into and out of the API. FastAPI uses these models automatically for request body parsing, response serialization, and JSON Schema generation in the OpenAPI docs.
  • user.py defines the User model with fields: id (optional string), name, age, and email.
  • task.py defines Task (for creation) and TaskUpdate (for partial updates via PUT), where TaskUpdate marks every field as Optional to allow clients to send only the fields they wish to change.
These models do not interact with MongoDB directly — they are purely for validation and schema documentation.
The schemas/ directory contains plain functions that convert raw MongoDB documents (Python dicts with an _id key containing a BSON ObjectId) into clean Python dicts whose id field is a plain string. This translation step is necessary because BSON ObjectId objects are not JSON-serializable and do not match the Optional[str] type declared on Pydantic models.
  • user.py exports user_entity() (single document) and users_entity() (list).
  • task.py exports task_entity(), tasks_entity(), and task_update_entity(). The task_update_entity() function additionally strips any None values from the update dict so that only provided fields are passed to MongoDB’s $set operator.
Each controller is a class (UserController, TaskController) that encapsulates all direct MongoDB operations for its resource. Controllers import mong_conn from app/config/db.py, the relevant Pydantic models from app/models/, and the serializer functions from app/schemas/.Implemented methods follow a consistent pattern:
  • get_all_*() — calls .find() and passes the cursor to the list serializer.
  • get_*(id) — calls .find_one() with an ObjectId filter; raises HTTP 404 if not found.
  • add_*(obj) — calls .insert_one(), then re-fetches the inserted document by inserted_id and returns the serialized entity.
  • update_*(id, obj) — calls .find_one_and_update() with $set; re-fetches via get_*() to return the updated state.
  • delete_*(id) — calls .find_one_and_delete(); returns HTTP 204 No Content on success.
A private __raise_404() helper is used across all not-found branches to keep error handling consistent.
Route files wire HTTP methods and URL paths to controller calls. Each file creates an APIRouter with a prefix (e.g., /users, /tasks) and a tags list for OpenAPI grouping, then defines endpoint functions decorated with @router.get, @router.post, @router.put, and @router.delete.Route functions are intentionally thin: they receive the request data, pass it to the controller, and return the result. All HTTPException instances raised by controllers are caught and re-raised so FastAPI can convert them to the appropriate HTTP error responses.

Request Flow

The following sequence describes the complete lifecycle of a single API request through the application:
  1. HTTP request arrives at a URL such as POST /users/.
  2. FastAPI matches the route in app/routes/user.py and invokes the add_user() handler.
  3. The handler calls the controller: user_ctrl.add_user(user.model_dump()) passes a plain dict to UserController.add_user().
  4. The controller writes to MongoDB: mong_conn.local.user.insert_one(user) inserts the document and returns an InsertOneResult.
  5. The controller re-fetches the document using the inserted_id to ensure the response reflects the stored state.
  6. The schema serializer runs: user_entity(__user) converts the raw MongoDB dict (with _id as ObjectId) into a clean dict (with id as str).
  7. FastAPI serializes the response against the response_model=User declaration and sends a JSON response to the client.
The collections mong_conn.local.user and mong_conn.local.task both reside in MongoDB’s built-in local database. This database is always present on any MongoDB instance, which makes it convenient for development — but it is not replicated in a replica set. For any production or staging deployment, replace local with a dedicated application database name (see the Configuration guide).

Build docs developers (and LLMs) love