diff --git a/server/python/.gitignore b/server/python/.gitignore
new file mode 100644
index 0000000..9d98c50
--- /dev/null
+++ b/server/python/.gitignore
@@ -0,0 +1,17 @@
+#-------------------------
+# Python / FastAPI backend
+#-------------------------
+
+# Byte-compiled / optimized files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Virtual environment
+.venv/
+
+# Environment variables
+.env
+
+#MacOS files
+.DS_Store
diff --git a/server/python/README.md b/server/python/README.md
new file mode 100644
index 0000000..504019a
--- /dev/null
+++ b/server/python/README.md
@@ -0,0 +1,123 @@
+# Welcome to the Python Backend
+
+TOC
+1. [Overview](#overview)
+2. [Getting Setup](#getting-setup)
+3. [Learning FastAPI](#learning-fastapi)
+4. [Exploring the Codebase](#exploring-the-codebase)
+5. [Feature Parity Status](#feature-parity-status)
+6. [Utilities and Nice to Know](#nice-to-know)
+
+
+## Overview
+We're porting our Express backend to Python/FastAPI to achieve **functional parity** - same endpoints, same responses, same MongoDB operations. This allows one frontend to work with multiple backends.
+
+
+**Important**: We're not rewriting - we're replicating behavior exactly. So this means we are figuring out the pythonic way of doing things. You will find that we can accomplish things with Fast in fewer lines of code than in Express.
+
+## Getting Setup
+0. Install Python if you don't already have it. (*Depending on your version of python, you might need to use python3 and pip3 vs python and pip*)
+1. Clone from my fork of the main repo.
+ ```sh
+ https://github.com/tmcneil-mdb/docs-sample-apps.git
+ ```
+
+2. Checkout my branch
+ ```sh
+ git checkout python-backend-setup
+ ```
+
+4. Verify you are on the right branch
+ ```sh
+ git branch
+ ```
+5. In the root directory. /python, make a virtual environment.
+ ```sh
+ python -m venv .venv
+ ```
+6. Activate the virtual environment
+ ```sh
+ source .venv/bin/activate
+ ```
+7. Navigate back to /python, install the required packages.
+ ```sh
+ pip install -r requirements.txt
+ ```
+8. Create an .env file at the /python root.
+It should have this format:
+ ```python
+ MONGO_URI="place your connection string here"
+ MONGO_DB="sample_mflix"
+ ```
+9. Navigate back to the root and start the server.
+ ```sh
+ fastapi dev main.py or uvicorn main:app --reload
+ ```
+ (NOTE: THESE WONT WORK IF THE VIRTUAL ENVIRONMENT IS NOT ACTIVATED)
+10. Click the link in the terminal or visit ```localhost:8000/docs```
+11. Try visiting ```localhost:8000/api/movies```.
+
+ If the setup ran correctly, you should see data 🎉
+
+*I recommend having your atlas instance up to explore your data and query the db directly.*
+
+**Troubleshooting**: If commands fail, ensure your virtual environment is activated (you should see `(.venv)` in your terminal prompt).
+
+
+## Learning FastAPI
+Before diving into the repo, I suggest spending some time with the official [FastAPI tutorial](https://fastapi.tiangolo.com/tutorial/).
+
+You only need to read up to the 'Request Body' section to get comfortable. *The query parameters and path parameters section can be read later.*
+
+Key Takeaways:
+- Decorators (@app.get, @app.post,etc.)
+- Query, path, and body parameters
+- Pydantic validation/ serialization
+- Automatic JSON responses
+- Built-in docs and testing at '/docs'
+
+
+## Exploring the Codebase
+Once you have completed the tutorial, I would suggest exploring the code and just noticing the differences between Express and Python. The apps are setup similarly but there are some small differences.
+
+### Architecture
+|Layer|Purpose|Express Equivalent| Differences|
+|:---|:-------|:----------|:------|
+|Routes
`src/routers/movies.py`| Defines all /movies endpoints (GET, POST, PUT, etc.)| /controllers/movieController.ts |The movies.ts file inside the routes file in the Express backend it actually wiring up the endpoints. Fast handles this for us in the main.py in one line. ```app.include_router(movies.router, prefix="/api/movies", tags=["movies"])```|
+|Models
`src/models/models.py`| Pydantic schema for validating and serializing request/response data.| /types/index.ts|There are some differences in how the models are constructed. Take note on how nested classes are handled.|
+|Utils
`src/utils/errorHandler.py`| Centralized utilities for responses, error handling and MongoDB exception mapping.|utils/errorHandler.ts |Express requires devs to write exception handling and validation on their own. Pydanic handles the validation and exception handling is a bit cleaner in Fast. You will notice the most differences here.|
+|Database
`src/database/mongo_client.py`| Handles the connection to the db|/config/database.ts| *The current database file does not have feature parity with the Express version*|
+
+
+## Feature Parity Status
+|Feature|Status|Owner|Notes|
+|:------|:-----|:----|:----|
+|Global Exception Handling| DONE| - |Found in Utils |
+|JSON Response Matching| DONE | - |Found in Utils|
+|CRUD- ```insertOne()```| Not Started| Angela| - |
+|CRUD- ```insertMany()```| Not Started| Taylor| - |
+|CRUD- ```findOne()```| Not Started| Angela| - |
+|CRUD- ```find()```| DONE| - | Found in movies.py as ```get_all_movies()``` This is a good function to look at to understand query parameters, requests and responses. Your functions will be simpler than this. But that is a good base to start with.|
+|CRUD- ```updateOne()```| Not Started| Angela| - |
+|CRUD- ```updateMany()```| Not Started| Taylor| - |
+|CRUD- ```deleteOne()```| Not Started| Angela| - |
+|CRUD- ```deleteMany()```| Not Started| Taylor| -|
+|CRUD- ```findOne()```| Not Started| Angela| - |
+|CRUD- ```findOneAndDelete()```| Not Started| Angela| This one is a bit harder but I figure this could be a fun challenge.|
+
+
+## Nice to Know
+### Utilities (errorHandler.py)
+This module provides a parity replacement for the Express errorHandler.ts file. It provides the same response shapes and error handling and removes the middleware.
+
+- create_success_response(data,message) - creates a success response with the same shape as the Express version.
+- create_error_response(message,code,details) - creates a error response with the same shape as the Express version.
+- parse_mongo_exception(exc) - ensures PyMongo exceptions return JSON identical to the Express Version
+- register_error_handlers - hooks our error system into the Fast app
+
+Fast automatically handles async and validation errors, so there is no need for asyncHandler or validateRequiredFields from the TS version.
+
+### Useful Links
+- [Main Repo]( https://github.com/mongodb/docs-sample-apps/tree/main )
+- [Sample App Scoping Doc](https://docs.google.com/document/d/12dROckw_Cp0ku2IIGku-ch7MvEuBPo0V4Gs-5wQgeHQ/edit?tab=t.0)
+- [Sample App Project Description & Breakdown Doc](https://docs.google.com/document/d/1xv2dmcNrT-HYk5TBE-KtVDPmW0274rpBZ0_0QmC66ac/edit?tab=t.0#heading=h.ki9tatw08ilc)
\ No newline at end of file
diff --git a/server/python/main.py b/server/python/main.py
new file mode 100644
index 0000000..a1a67e9
--- /dev/null
+++ b/server/python/main.py
@@ -0,0 +1,34 @@
+from fastapi import FastAPI
+from src.routers import movies
+from src.utils.errorHandler import register_error_handlers
+
+app = FastAPI()
+register_error_handlers(app)
+app.include_router(movies.router, prefix="/api/movies", tags=["movies"])
+
+
+
+
+
+
+
+
+#------------------------------------
+# Testing error endpoints. Will be removed later
+#------------------------------------
+
+'''
+@app.get("/")
+async def root():
+ return {"message": "Backend is running!"}
+
+@app.get("/test-duplicate")
+async def test_duplicate():
+ from pymongo.errors import DuplicateKeyError
+ raise DuplicateKeyError("This is a test duplicate key error.")
+
+@app.get("/test-generic")
+async def test_generic():
+ from pymongo.errors import PyMongoError
+ raise PyMongoError("This is a test generic pymongo error.")
+'''
\ No newline at end of file
diff --git a/server/python/requirements.in b/server/python/requirements.in
new file mode 100644
index 0000000..e8ee7b7
--- /dev/null
+++ b/server/python/requirements.in
@@ -0,0 +1,56 @@
+# ==============================================================================
+# 1. CORE WEB FRAMEWORK & ASGI SERVER
+# FastAPI and its main components.
+# ------------------------------------------------------------------------------
+fastapi~=0.120.0 # The main web framework
+starlette~=0.48.0 # FastAPI's underlying ASGI toolkit
+uvicorn~=0.38.0 # Production-ready ASGI server
+uvloop~=0.22.0 # Optional: High-performance event loop for uvicorn
+websockets~=15.0.0 # For WebSocket support
+watchfiles~=1.1.0 # For hot-reloading in development
+
+# ==============================================================================
+# 2. DATA VALIDATION & CORE UTILITIES
+# Primary libraries for data models and environment config.
+# ------------------------------------------------------------------------------
+pydantic~=2.12.0 # Data validation and settings management
+python-dotenv~=1.1.0 # For loading configuration from .env files
+python-multipart~=0.0.0 # For parsing form data and file uploads
+PyYAML~=6.0.0 # For handling YAML configuration or data
+
+# ==============================================================================
+# 3. DATABASE & CONNECTIVITY
+# Database driver and necessary utilities.
+# ------------------------------------------------------------------------------
+pymongo~=4.15.0 # MongoDB driver
+dnspython~=2.8.0 # Required for SRV record lookups by pymongo (e.g., MongoDB Atlas)
+
+# ==============================================================================
+# 4. HTTP CLIENT & UTILITIES
+# Primary libraries for making external HTTP requests.
+# ------------------------------------------------------------------------------
+httpx~=0.28.0 # Asynchronous HTTP client for requests to external APIs
+email-validator~=2.3.0 # Utility for validating email addresses
+
+# ==============================================================================
+# 5. CLI & DEVELOPMENT TOOLS
+# Tools for building command-line interfaces for management tasks.
+# ------------------------------------------------------------------------------
+typer~=0.20.0 # Library for creating command-line applications
+fastapi-cli~=0.0.0 # Tools to run and manage FastAPI projects
+fastapi-cloud-cli~=0.3.0 # Tools for cloud deployment (specific to your pipeline)
+
+# ==============================================================================
+# 6. TESTING & MONITORING
+# Frameworks for ensuring code quality and production health.
+# ------------------------------------------------------------------------------
+pytest~=8.4.0 # Primary testing framework
+pytest-asyncio~=1.2.0 # Plugin to make asynchronous tests easy with pytest
+sentry-sdk~=2.42.0 # For error tracking and performance monitoring
+
+# ==============================================================================
+# 7. LOGGING AND TERMINAL OUTPUT
+# Libraries for rich console output and debugging.
+# ------------------------------------------------------------------------------
+rich~=14.2.0 # For rich, formatted terminal output
+rich-toolkit~=0.15.0 # Extensions for the 'rich' library
\ No newline at end of file
diff --git a/server/python/requirements.txt b/server/python/requirements.txt
new file mode 100644
index 0000000..3beb0d8
--- /dev/null
+++ b/server/python/requirements.txt
@@ -0,0 +1,154 @@
+#
+# This file is autogenerated by pip-compile with Python 3.13
+# by the following command:
+#
+# pip-compile requirements.in
+#
+annotated-doc==0.0.3
+ # via fastapi
+annotated-types==0.7.0
+ # via pydantic
+anyio==4.11.0
+ # via
+ # httpx
+ # starlette
+ # watchfiles
+certifi==2025.10.5
+ # via
+ # httpcore
+ # httpx
+ # sentry-sdk
+click==8.3.0
+ # via
+ # rich-toolkit
+ # typer
+ # uvicorn
+dnspython==2.8.0
+ # via
+ # -r requirements.in
+ # email-validator
+ # pymongo
+email-validator==2.3.0
+ # via
+ # -r requirements.in
+ # pydantic
+fastapi==0.120.0
+ # via -r requirements.in
+fastapi-cli==0.0.14
+ # via -r requirements.in
+fastapi-cloud-cli==0.3.1
+ # via -r requirements.in
+h11==0.16.0
+ # via
+ # httpcore
+ # uvicorn
+httpcore==1.0.9
+ # via httpx
+httptools==0.7.1
+ # via uvicorn
+httpx==0.28.1
+ # via
+ # -r requirements.in
+ # fastapi-cloud-cli
+idna==3.11
+ # via
+ # anyio
+ # email-validator
+ # httpx
+iniconfig==2.3.0
+ # via pytest
+markdown-it-py==4.0.0
+ # via rich
+mdurl==0.1.2
+ # via markdown-it-py
+packaging==25.0
+ # via pytest
+pluggy==1.6.0
+ # via pytest
+pydantic[email]==2.12.3
+ # via
+ # -r requirements.in
+ # fastapi
+ # fastapi-cloud-cli
+pydantic-core==2.41.4
+ # via pydantic
+pygments==2.19.2
+ # via
+ # pytest
+ # rich
+pymongo==4.15.3
+ # via -r requirements.in
+pytest==8.4.2
+ # via
+ # -r requirements.in
+ # pytest-asyncio
+pytest-asyncio==1.2.0
+ # via -r requirements.in
+python-dotenv==1.1.1
+ # via
+ # -r requirements.in
+ # uvicorn
+python-multipart==0.0.20
+ # via -r requirements.in
+pyyaml==6.0.3
+ # via
+ # -r requirements.in
+ # uvicorn
+rich==14.2.0
+ # via
+ # -r requirements.in
+ # rich-toolkit
+ # typer
+rich-toolkit==0.15.1
+ # via
+ # -r requirements.in
+ # fastapi-cli
+ # fastapi-cloud-cli
+rignore==0.7.1
+ # via fastapi-cloud-cli
+sentry-sdk==2.42.1
+ # via
+ # -r requirements.in
+ # fastapi-cloud-cli
+shellingham==1.5.4
+ # via typer
+sniffio==1.3.1
+ # via anyio
+starlette==0.48.0
+ # via
+ # -r requirements.in
+ # fastapi
+typer==0.20.0
+ # via
+ # -r requirements.in
+ # fastapi-cli
+ # fastapi-cloud-cli
+typing-extensions==4.15.0
+ # via
+ # fastapi
+ # pydantic
+ # pydantic-core
+ # rich-toolkit
+ # typer
+ # typing-inspection
+typing-inspection==0.4.2
+ # via pydantic
+urllib3==2.5.0
+ # via sentry-sdk
+uvicorn[standard]==0.38.0
+ # via
+ # -r requirements.in
+ # fastapi-cli
+ # fastapi-cloud-cli
+uvloop==0.22.1
+ # via
+ # -r requirements.in
+ # uvicorn
+watchfiles==1.1.1
+ # via
+ # -r requirements.in
+ # uvicorn
+websockets==15.0.1
+ # via
+ # -r requirements.in
+ # uvicorn
diff --git a/server/python/src/database/mongo_client.py b/server/python/src/database/mongo_client.py
new file mode 100644
index 0000000..a608530
--- /dev/null
+++ b/server/python/src/database/mongo_client.py
@@ -0,0 +1,11 @@
+from pymongo import AsyncMongoClient
+from dotenv import load_dotenv
+import os
+
+load_dotenv()
+
+client = AsyncMongoClient(os.getenv("MONGO_URI"))
+db = client[os.getenv("MONGO_DB")]
+
+def get_collection(name:str):
+ return db[name]
\ No newline at end of file
diff --git a/server/python/src/models/models.py b/server/python/src/models/models.py
new file mode 100644
index 0000000..9e66a82
--- /dev/null
+++ b/server/python/src/models/models.py
@@ -0,0 +1,116 @@
+from pydantic import BaseModel, Field
+from datetime import datetime
+from typing import Optional, TypeVar, Generic, Any
+
+
+T = TypeVar("T")
+
+class Awards(BaseModel):
+ wins: Optional[int] = None
+ nominations: Optional[int] = None
+ text: Optional[str] = None
+
+class Imdb(BaseModel):
+ rating: Optional[float] = None
+ votes: Optional[int] = None
+ id: Optional[int] = None
+
+class Movie(BaseModel):
+ id: Optional[str] = Field(alias="_id")
+ title: str
+ year: Optional[int] = None
+ plot: Optional[str] = None
+ fullplot: Optional[str] = None
+ released: Optional[datetime] = None
+ runtime: Optional[int] = None
+ poster: Optional[str] = None
+ genres: Optional[list[str]] = None
+ directors: Optional[list[str]] = None
+ writers: Optional[list[str]] = None
+ cast: Optional[list[str]] = None
+ countries: Optional[list[str]] = None
+ languages: Optional[list[str]] = None
+ rated: Optional[str] = None
+ awards: Optional[Awards] = None
+ imdb: Optional[Imdb] = None
+
+ model_config = {
+ "populate_by_name" : True
+ }
+
+
+'''
+So this an interesting conversion. Pydanic doesn't cleanly support constructing
+models that have MongoDB query operators as field names. This becomes an issue when
+we want to convert the validated model back to a dictionary to use as a MongoDB query filter.
+For example, if a user leaves the 'q' parameter blank, we don't want to include the '$text' operator
+in the filter at all but validation will send an empty value for it and that causes errors. So
+I am handling the validation in the query router itself, but leaving this here as an example of how
+it could be done, if I am wrong about Pydantic's capabilities.
+'''
+
+class TextFilter(BaseModel):
+ search: str = Field(..., alias="$search")
+
+class RegexFilter(BaseModel):
+ regex: str = Field(..., alias="$regex")
+ options: Optional[str] = Field(None, alias="$options")
+
+class RatingFilter(BaseModel):
+ gte: Optional[float] = Field(None, alias="$gte")
+ lte: Optional[float] = Field(None, alias="$lte")
+
+class MovieFilter(BaseModel):
+ text: Optional[TextFilter] = Field(None, alias="$text")
+ genres: Optional[RegexFilter] = None
+ year: Optional[int] = None
+ imdb_rating: Optional[RatingFilter] = Field(None, alias="imdb.rating")
+
+ model_config = {
+ "populate_by_name" : True
+ }
+
+
+class Pagination(BaseModel):
+ page: int
+ limit: int
+ total: int
+ pages: int
+
+
+class CreateMovieRequest(BaseModel):
+ title: str
+ year: Optional[int] = None
+ plot: Optional[str] = None
+ fullplot: Optional[str] = None
+ genres: Optional[list[str]] = None
+ directors: Optional[list[str]] = None
+ writers: Optional[list[str]] = None
+ cast: Optional[list[str]] = None
+ countries: Optional[list[str]] = None
+ languages: Optional[list[str]] = None
+ rated: Optional[str] = None
+ runtime: Optional[int] = None
+ poster: Optional[str] = None
+
+
+
+class SuccessResponse(BaseModel, Generic[T]):
+ success: bool = True
+ message: Optional[str]
+ data: T
+ timestamp: str
+ pagination: Optional[Pagination] = None
+
+
+class ErrorDetails(BaseModel):
+ message: str
+ code: Optional[str]
+ details: Optional[Any] = None
+
+class ErrorResponse(BaseModel):
+ success: bool = False
+ message: str
+ error: ErrorDetails
+ timestamp: str
+
\ No newline at end of file
diff --git a/server/python/src/routers/movies.py b/server/python/src/routers/movies.py
new file mode 100644
index 0000000..efdf6dd
--- /dev/null
+++ b/server/python/src/routers/movies.py
@@ -0,0 +1,184 @@
+from fastapi import APIRouter, Query
+from src.database.mongo_client import db, get_collection
+from src.models.models import CreateMovieRequest, Movie, SuccessResponse
+from typing import List
+from datetime import datetime
+from src.utils.errorHandler import create_success_response, create_error_response
+import re
+
+'''
+This file contains all the business logic for movie operations.
+Each method demonstrates different MongoDB operations using the PyMongo driver.
+
+Implemented Endpoints:
+- GET /api/movies/ : Retrieve a list of movies with optional filter, sorting,
+ and pagination.
+- POST /api/movies/batch : Create multiple movies in a single request.
+
+
+'''
+router = APIRouter()
+#------------------------------------
+# Place get_movie_by_id endpoint here
+#------------------------------------
+
+
+
+"""
+ GET /api/movies/
+
+ Retrieve a list of movies with optional filtering, sorting, and pagination.
+
+ Query Parameters:
+ q (str, optional): Text search query (searches title, plot, fullplot).
+ genre (str, optional): Filter by genre.
+ year (int, optional): Filter by year.
+ min_rating (float, optional): Minimum IMDB rating.
+ max_rating (float, optional): Maximum IMDB rating.
+ limitNum (int, optional): Number of results to return (default: 20, max: 100).
+ skipNum (int, optional): Number of documents to skip for pagination (default: 0).
+ sortBy (str, optional): Field to sort by (default: "title").
+ sort_order (str, optional): Sort direction, "asc" or "desc" (default: "asc").
+
+ Returns:
+ SuccessResponse[List[Movie]]: A response object containing the list of movies and metadata.
+"""
+
+@router.get("/", response_model=SuccessResponse[List[Movie]])
+# Validate the query parameters using FastAPI's Query functionality.
+async def get_all_movies(
+ q:str = Query(default=None),
+ title: str = Query(default=None),
+ genre:str = Query(default=None),
+ year:int = Query(default=None),
+ min_rating:float = Query(default=None),
+ max_rating:float = Query(default=None),
+ limit:int = Query(default=20, ge=1, le=100),
+ skip:int = Query(default=0, ge=0),
+ sort_by:str = Query(default="title"),
+ sort_order:str = Query(default="asc")
+):
+ movies_collection = get_collection("movies")
+ filter_dict = {}
+ if q:
+ filter_dict["$text"] = {"$search": q}
+ if title:
+ filter_dict["title"] = {"$regex": title, "$options": "i"}
+ if genre:
+ filter_dict["genres"] = {"$regex": genre, "$options": "i"}
+ if year:
+ filter_dict["year"] = year
+ if min_rating is not None or max_rating is not None:
+ rating_filter = {}
+ if min_rating is not None:
+ rating_filter["$gte"] = min_rating
+ if max_rating is not None:
+ rating_filter["$lte"] = max_rating
+ filter_dict["imdb.rating"] = rating_filter
+
+ # Building the sort object based on user input
+ sort_order = -1 if sort_order == "desc" else 1
+ sort = [(sort_by, sort_order)]
+
+ # Query the database with the constructed filter, sort, skip, and limit.
+ cursor = movies_collection.find(filter_dict).sort(sort).skip(skip).limit(limit)
+ movies = []
+ async for movie in cursor:
+ movie["_id"] = str(movie["_id"]) # Convert ObjectId to string
+ # Ensure that the year field contains int value.
+ if "year" in movie and not isinstance(movie["year"], int):
+ cleaned_year = re.sub(r"\D", "", str(movie["year"]))
+ try:
+ movie["year"] = int(cleaned_year) if cleaned_year else None
+ except ValueError:
+ movie["year"] = None
+ movies.append(movie)
+
+ # Return the results wrapped in a SuccessResponse
+ return create_success_response(movies, f"Found {len(movies)} movies.")
+
+#------------------------------------
+# Place create_movie endpoint here
+#------------------------------------
+
+#------------------------------------
+# Place create_movies_batch endpoint here
+#------------------------------------
+
+'''
+POST /api/movies/batch
+
+Create multiple movies in a single request.
+
+Request Body:
+ movies (List[CreateMovieRequest]): A list of movie objects to insert. Each object should include:
+ - title (str): The movie title.
+ - year (int, optional): The release year.
+ - plot (str, optional): Short plot summary.
+ - fullplot (str, optional): Full plot summary.
+ - genres (List[str], optional): List of genres.
+ - directors (List[str], optional): List of directors.
+ - writers (List[str], optional): List of writers.
+ - cast (List[str], optional): List of cast members.
+ - countries (List[str], optional): List of countries.
+ - languages (List[str], optional): List of languages.
+ - rated (str, optional): Movie rating.
+ - runtime (int, optional): Runtime in minutes.
+ - poster (str, optional): Poster URL.
+
+ Returns:
+ SuccessResponse: A response object containing the number of inserted movies and their IDs.
+
+'''
+
+@router.post("/batch")
+async def create_movies_batch(movies: List[CreateMovieRequest]):
+ movies_collection = get_collection("movies")
+ movies_dicts = []
+ for movie in movies:
+ movies_dicts.append(movie.model_dump(exclude_unset=True, exclude_none=True))
+ result = await movies_collection.insert_many(movies_dicts)
+ return create_success_response({
+ "insertedCount": len(result.inserted_ids),
+ "insertedIds": [str(_id) for _id in result.inserted_ids]
+ },
+ f"Successfully created {len(result.inserted_ids)} movies."
+ )
+
+
+
+#------------------------------------
+# Place update_movie endpoint here
+#------------------------------------
+
+#------------------------------------
+# Place update_movies_by_batch endpoint here
+#------------------------------------
+
+#------------------------------------
+# Place delete_movie endpoint here
+#------------------------------------
+
+#------------------------------------
+# Place delete_movies_by_batch endpoint here
+#------------------------------------
+
+#------------------------------------
+# Place find_and_delete_movie endpoint here
+#------------------------------------
+
+
+# ---- Old testing endpoint, will be removed later ----
+'''
+# Testing the ErrorReponse Model
+@router.get("/error")
+async def test_error():
+ try:
+ raise ValueError("This is a test error.")
+ except ValueError as e:
+ return create_error_response(
+ message="A test error occurred.",
+ code="TEST_ERROR",
+ details=str(e)
+ )
+'''
\ No newline at end of file
diff --git a/server/python/src/utils/errorHandler.py b/server/python/src/utils/errorHandler.py
new file mode 100644
index 0000000..4258e3a
--- /dev/null
+++ b/server/python/src/utils/errorHandler.py
@@ -0,0 +1,129 @@
+from fastapi import Request
+from fastapi.responses import JSONResponse
+from pymongo.errors import PyMongoError, DuplicateKeyError, WriteError
+from datetime import datetime, timezone
+from typing import Any, Dict, Optional
+from src.models.models import ErrorDetails, ErrorResponse, SuccessResponse, T
+
+
+'''
+Open to having a conversation about parity in the code. From my understanding exception handeling, validation errors and enforcement(Pydantic),
+and error response formatting are all handled natively by FastAPI. So I don't believe I need to create the ValidationError
+class, middleware, or exception handlers present in the TS code.
+
+'''
+
+
+'''
+Creates a standardized success response.
+
+
+Args:
+ data (T): The data to include in the response.
+ message (Optional[str]): An optional message to include.
+
+Returns:
+ SuccessResponse[T]: A standardized success response object.
+ '''
+
+
+# TODO: Verify the timestamp format is acceptable.
+def create_success_response(data:T, message: Optional[str] = None) -> SuccessResponse[T]:
+ return SuccessResponse(
+ message=message or "Operation completed successfully.",
+ data=data,
+ timestamp=datetime.now(timezone.utc).isoformat() + "Z",
+
+ )
+
+
+'''
+Creates a standardized error response.
+
+Args:
+ message (str): The error message.
+ code (Optional[str]): An optional error code.
+ details (Optional[Any]): Additional error details.
+
+Returns:
+ ErrorResponse: A standardized error response object.
+
+'''
+
+# TODO: Verify the timestamp format is acceptable.
+def create_error_response(message: str, code: Optional[str]=None, details: Optional[Any]=None) -> ErrorResponse:
+ return ErrorResponse(
+ message=message,
+ error=ErrorDetails(
+ message=message,
+ code=code,
+ details=details
+ ),
+ timestamp=datetime.now(timezone.utc).isoformat() + "Z",
+ )
+
+
+
+'''
+This is interesting, I am not sure if this is worth explaining that compared to Node, you are
+not going to get exceptions thrown from MongoDB operations in the same way. You are not getting
+error codes back from operations, you are getting exceptions.
+'''
+
+def parse_mongo_exception(exc: Exception) -> dict:
+ if isinstance(exc, DuplicateKeyError):
+ return{
+ "message": "Duplicate key error occurred.",
+ "code": "DUPLICATE_KEY_ERROR",
+ "details": "A document with the same key already exists.",
+ "statusCode":409
+ }
+
+ # This is stating that the data that you are trying to implement is the wrong shape
+ # for the schema implemented in MongoDB.
+ elif isinstance(exc, WriteError):
+ return{
+ "message": "Document validation failed.",
+ "code": "WRITE_ERROR",
+ "details": str(exc),
+ "statusCode":400
+ }
+
+ elif isinstance(exc, PyMongoError):
+ return {
+ "message" : "A database error occurred.",
+ "code": "DATABASE_ERROR",
+ "details": str(exc),
+ "statusCode":500
+ }
+ return {
+ "message": "An unknown error occurred.",
+ "code": "UNKNOWN_ERROR",
+ "details": str(exc),
+ "statusCode": 500
+ }
+
+def register_error_handlers(app):
+
+ @app.exception_handler(PyMongoError)
+ async def mongo_exception_handler(request: Request, exc: PyMongoError):
+ error_details = parse_mongo_exception(exc)
+ return JSONResponse(
+ status_code = error_details["statusCode"],
+ content=create_error_response(
+ message=error_details["message"],
+ code=error_details["code"],
+ details=error_details["details"]
+ ).model_dump()
+ )
+
+ @app.exception_handler(Exception)
+ async def generic_exception_handler(request: Request, exc: Exception):
+ return JSONResponse(
+ status_code=500,
+ content=create_error_response(
+ message=str(exc),
+ code="INTERNAL_SERVER_ERROR",
+ details=getattr(exc, 'detail', None) or getattr(exc, 'args', None)
+ ).model_dump()
+ )
\ No newline at end of file