AI News Hub Logo

AI News Hub

FastAPI With LangChain and MongoDB

DEV Community
MongoDB Guests

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. Python, virtualenv basics Basic understanding of REST APIs MongoDB basics (collections, documents) Accounts/keys MongoDB Atlas or local MongoDB LLM provider API key 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. 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. 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. 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. OpenAI, though this may incur additional costs. 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 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 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:[email protected]/ 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/ 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 config.py import os MONGO_URI = os.getenv("MONGO_URI", "mongodb+srv://admin:[email protected]/") 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 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"] 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 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 = "\n".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.\n\n" f"Context:\n{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) 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) 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. 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": "[email protected]", "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": "[email protected]", "password": "string" } The above command will create a new user with hashed password: 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&[email protected]&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" } 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" } 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.\n\n" f"Context:\n{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:\n\n**Saving More Money:**\n\n1. **Track your expenses**: Write down every single transaction, no matter how small, for a week or two to understand where your money is going.\n2. **Create a budget**: Based on your income and expenses, allocate a specific amount for savings each month.\n3. **Automate your savings**: Set up an automatic transfer from your checking account to your savings account.\n4. **Cut back on unnecessary expenses**: Identify areas where you can cut back on unnecessary spending, such as dining out or subscription services.\n5. **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.).\n\n**Finishing Your Project:**\n\n1. **Break down your project into smaller tasks**: Divide your project into manageable tasks to make it feel less overwhelming.\n2. **Create a schedule**: Set a specific deadline for each task and stick to it.\n3. **Prioritize your tasks**: Focus on the most critical tasks first, and then move on to less important ones.\n4. **Remove distractions**: Identify potential distractions (e.g., social media, email, phone notifications) and eliminate them while you work.\n5. **Take regular breaks**: Take short breaks to recharge and maintain productivity.\n\nWhich of these steps do you want to start with?", "used_context": true } 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 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.