FastAPI With LangChain and MongoDB

Dev.to / 4/17/2026

💬 OpinionDeveloper Stack & InfrastructureTools & Practical UsageModels & Research

Key Points

  • The tutorial provides a complete, copy-and-paste example that integrates FastAPI, LangChain, and MongoDB into a working application.
  • It targets a real suggestion/idea-generation use case by combining personalized recommendations with checks for existing companies offering similar services.
  • FastAPI is used to quickly implement the backend REST API layer with minimal code, suitable for modern AI and microservice-style applications.
  • MongoDB serves as a flexible, document-based datastore for the application’s information, enabling scalable and evolving data structures.
  • LangChain is used to build LLM-powered functionality by connecting models with tools, memory, external data, and multi-step reasoning.

This article was written by Carlos Barboza.

I'm happy to welcome you to this tutorial! This demonstration provides a fully functional example of integrating FastAPI, LangChain, and MongoDB, and I'm eager to share it with all technology enthusiasts.

The value of this guide is that it provides a working example—you can copy and paste the code, and it will run seamlessly without any further configuration. It will also give you a clear, practical overview of the kinds of applications you can build using these powerful tools.

I am developing a suggestion application designed to assist customers globally in generating new ideas. This involves providing personalized recommendations and checking for existing companies that offer comparable services.

Prerequisites

  • Python, virtualenv basics
  • Basic understanding of REST APIs
  • MongoDB basics (collections, documents)
  • Accounts/keys
    • MongoDB Atlas or local MongoDB
    • LLM provider API key

Project Architecture


FastAPI is a Python framework for building APIs quickly, efficiently, and with very little code.

It’s known for being fast (as the name implies), easy to use, and perfect for modern applications like AI, microservices, and backend APIs. In this example, it will handle the API queries for the backend.

MongoDB is a NoSQL, document-based database that stores data in flexible JSON-like documents instead of rigid tables like SQL. This makes it fast to develop, easy to scale, and great for evolving data structures. For our case, it will store all the information.

LangChain is a framework for building applications powered by LLMs (like GPT, LLaMA, Ollama, and Mistral) that lets you connect models with tools, memory, external data, and multi-step reasoning. It provides the necessary tools in Python for utilizing AI models.

Project Setup

MongoDB Atlas

The simplest way to obtain a MongoDB instance for this guide is to register on the official site. This will provide you with an M0 instance, which is entirely sufficient for executing this guide.

Docker

Should you not have Docker installed, the official link provides installation instructions based on your operating system.

The docker-compose file is configured to set up a MongoDB container. This container uses the official version 7.0, is named mongodb, and uses the credentials root as the username and example as the password. Furthermore, a volume is defined to ensure data persistence across container reloads.

version: "3.3"
services:
  mongo:
    image: mongo:7.0
    container_name: mongodb
    restart: unless-stopped
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      # Named volume for persistent data
      - mongo_data:/data/db

volumes:
  mongo_data:

Execute the following commands to execute the docker:

docker compose up -d

Finally, you can connect to the MongoDB instance using the connection string. In your case, it’s mongodb://root:example@localhost:27017.

LLMs

Large language models (LLMs) are AI models trained on massive amounts of text that can understand language, generate text, write code, answer questions, and hold conversations like a human.

They don’t think—they predict the next word intelligently based on patterns they've learned. For our case, we are going to use Ollama so you can execute the example on your local machine.
Alternatively, you may choose to use a different LLM, such as OpenAI, though this may incur additional costs.

Ollama installation

Depending on your operating system, you must follow the official instructions. At the end of the day, you should be able to execute Ollama commands in your terminal.

ollama list # Shows all the models available in your terminal
ollama pull llama3.2 # Download the llama3.2 model

Development

Requirements.txt

These are all the libraries needed for the project:

aiohappyeyeballs==2.6.1
aiohttp==3.13.2
aiosignal==1.4.0
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.0
attrs==25.4.0
bcrypt==3.2.2
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
click==8.3.1
colorama==0.4.6
cryptography==46.0.3
dataclasses-json==0.6.7
distro==1.9.0
dnspython==2.8.0
ecdsa==0.19.1
email-validator==2.3.0
fastapi==0.123.0
frozenlist==1.8.0
greenlet==3.2.4
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
httpx-sse==0.4.3
idna==3.11
jiter==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
langchain==1.1.0
langchain-classic==1.0.0
langchain-community==0.4.1
langchain-core==1.1.0
langchain-openai==1.1.0
langchain-text-splitters==1.0.0
langgraph==1.0.4
langgraph-checkpoint==3.0.1
langgraph-prebuilt==1.0.5
langgraph-sdk==0.2.10
langsmith==0.4.49
marshmallow==3.26.1
motor==3.7.1
multidict==6.7.0
mypy_extensions==1.1.0
numpy==2.3.5
openai==2.8.1
orjson==3.11.4
ormsgpack==1.12.0
packaging==25.0
passlib==1.7.4
propcache==0.4.1
pyasn1==0.6.1
pycparser==2.23
pydantic==2.12.5
pydantic-settings==2.12.0
pydantic_core==2.41.5
pymongo==4.15.4
python-dotenv==1.2.1
python-jose==3.5.0
python-multipart==0.0.20
PyYAML==6.0.3
regex==2025.11.3
requests==2.32.5
requests-toolbelt==1.0.0
rsa==4.9.1
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.44
starlette==0.50.0
tenacity==9.1.2
tiktoken==0.12.0
tqdm==4.67.1
typing-inspect==0.9.0
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.5.0
uvicorn==0.38.0
xxhash==3.6.0
yarl==1.22.0
zstandard==0.25.0

Environment file

It’s recommended to have an .env file with sensitive information like the MongoDB URI, JWT Secret Key, and other settings. This file should be excluded from the git.ignore and must be configured on the main location.

MONGO_URI=mongodb+srv://admin:admin@atlascluster.f3spasq.mongodb.net/
MONGO_DB_NAME=fastapi_demo
JWT_SECRET_KEY=super-secret-key
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=60
OLLAMA_HOST=http://host.docker.internal:11434 # Only if fastAPI is running with docker

.gitignore

# ---------------------
# Environment variables
# ---------------------
.env
.env.local
.env.*.local

# ---------------------
# Python
# ---------------------
__pycache__/
*.pyc
*.pyo
*.pyd
*.pdb
*.pkl
*.db

# Virtual environments
venv/
.venv/

# Byte-compiled
*.py[cod]
*$py.class

# ---------------------
# FastAPI / Uvicorn logs
# ---------------------
*.log

# ---------------------
# IDE & editor files
# ---------------------
.vscode/
.idea/
*.swp

# ---------------------
# OS files
# ---------------------
.DS_Store
Thumbs.db

# ---------------------
# Docker
# ---------------------
/data/
docker-data/
*.pid

# ---------------------
# Optional cache folders
# ---------------------
cache/
logs/

Schema folder

chat.py
from pydantic import BaseModel

class ChatRequest(BaseModel):
    question: str

class ChatResponse(BaseModel):
    answer: str
    used_context: bool
item.py
from pydantic import BaseModel

class Item(BaseModel):
    id: str
    name: str
    price: float
user.py
from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):
    email: EmailStr
    password: str

class UserPublic(BaseModel):
    id: str
    email: EmailStr

class Token(BaseModel):
    access_token: str
    token_type: str = "bearer"

class UserInDB(BaseModel):
    id: str
    email: EmailStr
    hashed_password: str
notes.py
from pydantic import BaseModel

class NotesCreate(BaseModel):
    text: str

class NotesInDB(BaseModel):
    id: str
    user_id: str
    text: str

Core folder

config.py
import os

MONGO_URI = os.getenv("MONGO_URI", "mongodb+srv://admin:admin@atlascluster.f3spasq.mongodb.net/")
MONGO_DB_NAME = os.getenv("MONGO_DB_NAME", "fastapi_demo")

JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "change-me")
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
llm.py
import os
from langchain_community.chat_models import ChatOllama

OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")

llm = ChatOllama(
    model="llama3.2"
    ,base_url=OLLAMA_HOST,
)
security.py
from datetime import datetime, timedelta, timezone
from typing import Optional

from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from passlib.context import CryptContext

from app.core.config import JWT_SECRET_KEY, JWT_ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def create_access_token(
    data: dict,
    expires_delta: Optional[timedelta] = None,
) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + (
        expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
    return encoded_jwt

db folder

from motor.motor_asyncio import AsyncIOMotorClient
from app.core import config as core_config

client = AsyncIOMotorClient(core_config.MONGO_URI)
db = client[core_config.MONGO_DB_NAME]

users_collection = db["users"]
notes_collection = db["notes"]
items_collection = db["items"]

depsFolder

auth.py
from typing import Optional, Annotated

from fastapi import Depends, HTTPException, status
from jose import JWTError, jwt

from app.core.config import JWT_SECRET_KEY, JWT_ALGORITHM
from app.core.security import oauth2_scheme, hash_password, verify_password
from app.db.mongo import users_collection
from app.schemas.user import UserInDB, UserCreate

async def get_user_by_email(email: str) -> Optional[UserInDB]:
    doc = await users_collection.find_one({"email": email})
    if not doc:
        return None
    return UserInDB(
        id=str(doc["_id"]),
        email=doc["email"],
        hashed_password=doc["hashed_password"],
    )

async def create_user(user: UserCreate) -> UserInDB:
    existing = await get_user_by_email(user.email)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered",
        )
    hashed = hash_password(user.password)
    res = await users_collection.insert_one(
        {"email": user.email, "hashed_password": hashed}
    )
    return UserInDB(id=str(res.inserted_id), email=user.email, hashed_password=hashed)

async def get_current_user(
    token: Annotated[str, Depends(oauth2_scheme)]
) -> UserInDB:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = await get_user_by_email(email)
    if user is None:
        raise credentials_exception
    return user

Routers folder

auth.py
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm

from app.core.security import create_access_token, verify_password
from app.deps.auth import create_user, get_user_by_email, get_current_user
from app.schemas.user import UserCreate, UserPublic, Token, UserInDB

router = APIRouter(prefix="/auth", tags=["auth"])

@router.post("/register", response_model=UserPublic)
async def register(user: UserCreate):
    new_user = await create_user(user)
    return UserPublic(id=new_user.id, email=new_user.email)

@router.post("/login", response_model=Token)
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    # I am using email as the username field in the OAuth2 form.
    user = await get_user_by_email(form_data.username)
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
        )
    access_token = create_access_token(data={"sub": user.email})
    return Token(access_token=access_token)

@router.get("/me", response_model=UserPublic)
async def get_me(
    current_user: Annotated[UserInDB, Depends(get_current_user)]
):
    return UserPublic(id=current_user.id, email=current_user.email)
chat.py
from typing import Annotated

from fastapi import APIRouter, Depends
from langchain_core.messages import HumanMessage, SystemMessage

from app.core.llm import llm
from app.db.mongo import notes_collection
from app.deps.auth import get_current_user
from app.schemas.user import UserInDB
from app.schemas.chat import ChatRequest, ChatResponse

router = APIRouter(prefix="/chat", tags=["chat"])

@router.post("/", response_model=ChatResponse)
async def chat_with_ai(
    body: ChatRequest,
    current_user: Annotated[UserInDB, Depends(get_current_user)],
    # Uses LangChain + OpenAI + some context from MongoDB.
):
    print(current_user)

    notes_cursor = (
        notes_collection.find({"user_id": current_user.id})
        .sort("_id", -1)
        .limit(3)
    )
    notes = await notes_cursor.to_list(length=3)

    context_text = "
".join(
        note.get("text", "") for note in notes if note.get("text")
    )
    used_context = bool(context_text)

    system_prompt = (
        "You are an assistant helping the user with their notes."
        " "
        "Use the following context if relevant; if not, just answer normally.

"
        f"Context:
{context_text or '(no context available)'}"
    )
    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=body.question),
    ]

    response = await llm.ainvoke(messages)

    return ChatResponse(answer=response.content, used_context=used_context)
items.py
from typing import List, Annotated

from fastapi import APIRouter, Depends

from app.db.mongo import items_collection
from app.schemas.item import Item
from app.schemas.user import UserInDB
from app.deps.auth import get_current_user

router = APIRouter(prefix="/items", tags=["items"])

@router.get("/", response_model=List[Item])
async def list_items(
    current_user: Annotated[UserInDB, Depends(get_current_user)]
):
    # Example async Mongo query using Motor.
    # Demo: seed items if collection is empty
    count = await items_collection.count_documents({})
    if count == 0:
        await items_collection.insert_many(
            [
                {"name": "Book", "price": 10.5},
                {"name": "Laptop", "price": 999.99},
            ]
        )

    docs = await items_collection.find().to_list(length=100)
    return [
        Item(id=str(doc["_id"]), name=doc["name"], price=float(doc["price"]))
        for doc in docs
    ]
notes.py
from typing import Annotated

from fastapi import APIRouter, Depends
from langchain_core.messages import HumanMessage, SystemMessage

from app.core.llm import llm
from app.db.mongo import notes_collection
from app.deps.auth import get_current_user
from app.schemas.user import UserInDB
from app.schemas.chat import ChatRequest, ChatResponse
from app.schemas.notes import NotesCreate, NotesInDB

router = APIRouter(prefix="/notes", tags=["notes"])

@router.post("/", response_model=NotesInDB)
async def create_note(body: NotesCreate,current_user: Annotated[UserInDB, Depends(get_current_user)]):
    print(current_user)

    note = await notes_collection.insert_one({"text": body.text, "user_id": current_user.id})
    return NotesInDB(id=str(note.inserted_id), text=body.text, user_id=current_user.id)

Main.py

from fastapi import FastAPI

from app.routers import auth, items, chat, notes

app = FastAPI(title="FastAPI + MongoDB + JWT + LangChain Example")

@app.get("/")
async def root():
    return {"message": "FastAPI + MongoDB + JWT + LangChain example"}

# Include routers
app.include_router(auth.router)
app.include_router(items.router)
app.include_router(chat.router)
app.include_router(notes.router)

Application execution

On the main folder, you can execute the following command:

uvicorn app.main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

If you access the page http://127.0.0.1:8000/docs#, you can find the Swagger documentation created by default.

Users creation

You can execute the following command to create a new user:

curl -X 'POST' \
  'http://127.0.0.1:8000/auth/register' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "email": "user@example.com",
  "password": "string" 
}'

Or if you have vscode Rest Api Extension you can execute
POST http://127.0.0.1:8000/auth/register
Accept: application/json
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "string"
}

The above command will create a new user with hashed password:

Users authentication

In order to authenticate the user and retrieve the bearer token, please execute the following command:

curl -X 'POST' \
  'http://127.0.0.1:8000/auth/login' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&username=user%40example.com&password=string&scope=&client_id=string&client_secret=string'
Rest API extension
POST http://127.0.0.1:8000/auth/login
Content-Type: application/x-www-form-urlencoded
accept: application/json

grant_type=password&username=test@example.com&password=123&scope=&client_id=string&client_secret=string

After a successful authentication, you will receive the following output. The token is important to authenticate on the remaining routes.

HTTP/1.1 200 OK
date: Tue, 02 Dec 2025 17:40:11 GMT
server: uvicorn
content-length: 180
content-type: application/json
Connection: close

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QGV4YW1wbGUuY29tIiwiZXhwIjoxNzY0NzAwODExfQ.riXj1vvLZSyzyU4T7kG3ygYaUU8sgvgCjK_Os_uggvo",
  "token_type": "bearer"
}

Idea creation

The main purpose of the application is to make suggestions based on ideas that the end users have. The following request will create those notes.

POST http://127.0.0.1:8000/notes/
Content-Type: application/json
Authorization: Bearer {{token}}

{
  "text": "testing123"
}

Chat interaction

The application now offers advice based on the notes stored by users in the database. To enable interaction with the LLM, we define the chat's behavior in the chat.py file. This definition is an example of prompt engineering, and this is the section where you can modify the prompt's language and content, if needed.

system_prompt = (
        "You are an assistant helping the user with their notes."
        " "
        "Use the following context if relevant; if not, just answer normally.

"
        f"Context:
{context_text or '(no context available)'}"
    )

Execution example:

POST http://127.0.0.1:8000/chat/
Content-Type: application/json
Authorization: Bearer {{token}}

{
  "question": "What i need to do"
}

Execution output:

{
  "answer": "To help you achieve your goals of saving more money and finishing your project, here are some actionable steps:

**Saving More Money:**

1. **Track your expenses**: Write down every single transaction, no matter how small, for a week or two to understand where your money is going.
2. **Create a budget**: Based on your income and expenses, allocate a specific amount for savings each month.
3. **Automate your savings**: Set up an automatic transfer from your checking account to your savings account.
4. **Cut back on unnecessary expenses**: Identify areas where you can cut back on unnecessary spending, such as dining out or subscription services.
5. **Consider a savings challenge**: Try a savings challenge like the \"52-week savings challenge\" where you save an amount equal to the number of the week (e.g., Week 1: Save $1, Week 2: Save $2 etc.).

**Finishing Your Project:**

1. **Break down your project into smaller tasks**: Divide your project into manageable tasks to make it feel less overwhelming.
2. **Create a schedule**: Set a specific deadline for each task and stick to it.
3. **Prioritize your tasks**: Focus on the most critical tasks first, and then move on to less important ones.
4. **Remove distractions**: Identify potential distractions (e.g., social media, email, phone notifications) and eliminate them while you work.
5. **Take regular breaks**: Take short breaks to recharge and maintain productivity.

Which of these steps do you want to start with?",
"used_context": true
}

Docker (optional)

Our application is currently functional. However, to execute it in a different environment, we must install the necessary dependencies. Docker can facilitate and streamline this deployment process.

Create Dockerfile and paste the following code:

# Dockerfile
FROM python:3.11-slim

# Set work directory inside the container
WORKDIR /app

# Install system dependencies (optional but useful for many libs)
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Copy only requirements first (for better Docker cache)
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of your project
COPY . .

# Expose the FastAPI port
EXPOSE 8000

# Default command: run uvicorn
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Create the docker-compose.yml:

version: "3.8"
services:
  fastapi:
    build: .
    container_name: fastapi-langchain
    ports:
      - "8000:8000"
    env_file:
      - .env # optional: if you have one
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped

Now, we can build and execute our docker:

docker-compose build
docker-compose up -d

Conclusion

This tutorial has provided a complete, practical demonstration of building a modern, data-driven application by seamlessly integrating FastAPI, LangChain, and MongoDB.

We have successfully covered:

  • FastAPI for creating a robust, fast, and documented REST API, including user registration and JWT-based authentication.
  • MongoDB (using Motor) for asynchronous and scalable document storage, handling user data and application-specific notes.
  • LangChain (with Ollama) for enabling powerful AI capabilities, using the stored user notes as dynamic context to generate personalized, helpful suggestions and answers.

The example application, an idea generation assistant, serves as a clear blueprint for how to leverage this stack for complex projects requiring data persistence, secure user access, and cutting-edge large language model integration. By using a local LLM via Ollama, we also ensured the entire stack is executable on a local machine, making it accessible for rapid prototyping and development.

This integration strategy—combining the speed of FastAPI, the flexibility of MongoDB, and the intelligence of LangChain—is a highly effective pattern for developing the next generation of intelligent web applications. We hope this guide serves as a solid foundation for your future AI-powered projects.