diff --git a/snooty.toml b/snooty.toml
index 125cb037..c888bf9c 100644
--- a/snooty.toml
+++ b/snooty.toml
@@ -12,7 +12,8 @@ toc_landing_pages = [
"/crud/query",
"/crud/update",
"/monitoring-and-logging",
- "/reference"
+ "/reference",
+ "/integrations"
]
intersphinx = [
diff --git a/source/connect.txt b/source/connect.txt
index 2d20ac93..ae59e29e 100644
--- a/source/connect.txt
+++ b/source/connect.txt
@@ -100,6 +100,8 @@ see the corresponding code:
uri = "mongodb://localhost:27017/"
client = AsyncMongoClient(uri)
+.. _pymongo_connect_atlas:
+
Atlas
~~~~~
diff --git a/source/includes/integrations/fastapi-browser.png b/source/includes/integrations/fastapi-browser.png
new file mode 100644
index 00000000..e0568a81
Binary files /dev/null and b/source/includes/integrations/fastapi-browser.png differ
diff --git a/source/integrations.txt b/source/integrations.txt
index 41eb6a35..ba445c30 100644
--- a/source/integrations.txt
+++ b/source/integrations.txt
@@ -18,6 +18,10 @@ Third-Party Integrations and Tools
.. meta::
:keywords: pypi, package, web, module, pip
+.. toctree::
+
+ FastAPI Integration Tutorial
+
Overview
--------
diff --git a/source/integrations/fastapi-integration.txt b/source/integrations/fastapi-integration.txt
new file mode 100644
index 00000000..5389de18
--- /dev/null
+++ b/source/integrations/fastapi-integration.txt
@@ -0,0 +1,589 @@
+.. _pymongo-fastapi:
+.. original URL: https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/
+
+=============================
+Tutorial: FastAPI Integration
+=============================
+
+.. contents:: On this page
+ :local:
+ :backlinks: none
+ :depth: 3
+ :class: singlecol
+
+.. facet::
+ :name: genre
+ :values: tutorial
+
+.. meta::
+ :description: Build an application using FastAPI that integrates with a MongoDB deployment by using the PyMongo Async driver.
+ :keywords: quick start, tutorial, basics, code example
+
+Overview
+--------
+
+`FastAPI `__ is a modern, high-performance,
+production-ready asynchronous Python web framework designed for building APIs
+using standard Python type hints. In this tutorial, you can learn how to build a
+complete CRUD application that integrates MongoDB and FastAPI.
+
+Non-Blocking Architecture
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+FastAPI’s asynchronous, non-blocking architecture allows it to handle thousands
+of concurrent requests without relying on multi-threading. It uses Python’s
+``asyncio`` event loop to manage concurrency through coroutines. This allows the
+application to suspend a request while waiting queries to return. This reduces
+memory usage and can reduce latency.
+
+Built-in Features and Integrations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+FastAPI has several built-in integrations and features including the following
+components:
+
+- Pydantic for automatic data parsing, validation, and serialization
+- OpenAPI and JSON Schema for auto-generated, interactive API documentation
+- A lightweight, Pythonic Dependency Injection system for clean and testable
+ code
+- Support for OAuth2 and JWT authentication and authorization
+
+These features minimize boilerplate code to simplify development. For more
+information about FastAPI's capabilities, see the `FastAPI website
+`__.
+
+Tutorial
+--------
+
+You can find the completed sample app for this tutorial in the :github:`MongoDB
+with FastAPI sample project ` GitHub
+repository.
+
+Prerequisites
+~~~~~~~~~~~~~
+
+Ensure you have the following components installed and set up before you start
+this tutorial:
+
+- Python v3.9.0 or later.
+- A MongoDB Atlas cluster.
+ To learn how to set up a cluster, see
+ the :ref:`Getting Started ` guide for more information.
+
+Set-up
+~~~~~~
+
+Install dependencies, connect to MongoDB and start your FastAPI server:
+
+.. procedure::
+ :style: connected
+
+ .. step:: Clone the example code example
+
+ Run the following command in your terminal to clone the code from the
+ :github:`mongodb-pymongo-fastapi
+ ` GitHub repository:
+
+ .. code-block:: shell
+
+ git clone git@github.com:mongodb-developer/mongodb-pymongo-fastapi.git
+ cd mongodb-pymongo-fastapi
+
+ .. step:: Install the required dependencies
+
+ .. tip:: Activate a virtual environment
+
+ Installing your Python dependencies in a `virtualenv
+ `__ allows you to install
+ versions of your libraries for individual projects. Before running any
+ ``pip`` commands, ensure your ``virtualenv`` is active.
+
+ Run the following command in your terminal to install the dependencies
+ listed in the ``requirements.txt`` file:
+
+ .. code-block:: shell
+
+ cd mongodb-pymongo-fastapi
+ pip install -r requirements.txt
+
+ It might take a few moments to download and install your dependencies.
+
+ .. step:: Retrieve your connection string
+
+ Follow the :manual:`Find Your MongoDB Atlas Connection String guide
+ `
+ to retrieve your connection string.
+
+ Run the following code in your terminal to create an environment variable
+ to store your connection string:
+
+ .. code-block:: shell
+
+ export MONGODB_URL="mongodb+srv://:@/?retryWrites=true&w=majority"
+
+ .. tip:: Reset Environment Variables
+
+ Anytime you start a new terminal session, you will must reset this
+ environment variable. You can use `direnv `__ to
+ make this process easier.
+
+Connect the Application to Your Cluster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All the code for the example application is stored in the :github:`app.py file
+in the mongodb-pymongo-fastapi
+` GitHub
+repository.
+
+Use this code to perform the following actions:
+
+1. :ref:`Connect to your MongoDB Atlas cluster ` by using
+ the ``AsyncMongoClient()`` method with the ``MONGODB_URL`` environment
+ variable and specifying the database named ``college``.
+#. Create a pointer to the ``college`` database.
+#. Create a pointer to the ``students`` collection
+
+.. code-block:: python
+
+ client = AsyncMongoClient(os.environ["MONGODB_URL"],server_api=pymongo.server_api.ServerApi(version="1", strict=True,deprecation_errors=True))
+ db = client.get_database("college")
+ student_collection = db.get_collection("students")
+
+Review the Database Models
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This application has three models, the ``StudentModel``, the
+``UpdateStudentModel``, and the ``StudentCollection``. These models are
+defined in the ``app.py`` file.
+
+All the models in the application build on the `Pydantic
+`__ ``BaseModel``, which provides basic
+type validation, JSON parsing and serialization, and basic error handling.
+
+StudentModel Class
+```````````````````
+
+``StudentModel`` is the primary model used as the `response model
+`__ for the
+majority of the endpoints.
+
+MongoDB uses ``_id`` as the default identifier for documents. However, in
+Pydantic, field names that start with an underscore are treated as private
+attributes and cannot be assigned values directly. To work around this, we can
+name the field ``id`` in the Pydantic model, but given an alias of ``_id`` so it
+maps correctly to MongoDB.
+
+The ``id`` field is of type ``PyObjectId``, a custom type annotated with a
+``BeforeValidator`` that coerces the value into a string. This ensures that
+MongoDB's ``ObjectId`` can be accepted and serialized properly by the model.
+
+This setup requires the following ``model_config`` options:
+
+- ``populate_by_name=True``: Allows the model to be initialized using either the
+ field name (``id``) or its alias (``_id``)
+- ``arbitrary_types_allowed=True``: Enables support for custom types such as
+ ``PyObjectId``
+
+The ``id`` field is also defined as optional with a default value of ``None``,
+so a new ``StudentModel`` instance can be created without specifying an ``id``.
+MongoDB automatically generates an ``_id`` when the document is inserted, and
+this value is returned in API responses.
+
+The ``model_config`` also includes a ``json_schema_extra`` setting that defines
+example data used in FastAPI's autogenerated OpenAPI (Swagger) documentation.
+
+You can see the ``StudentModel`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ # Represents an ObjectId field in the database.
+ # It will be represented as a `str` on the model so that it can be serialized to JSON.
+ PyObjectId = Annotated[str, BeforeValidator(str)]
+
+ class StudentModel(BaseModel):
+ """
+ Container for a single student record.
+ """
+
+ # The primary key for the StudentModel, stored as a `str` on the instance.
+ # This will be aliased to ``_id`` when sent to MongoDB,
+ # but provided as ``id`` in the API requests and responses.
+ id: Optional[PyObjectId] = Field(alias="_id", default=None)
+ name: str = Field(...)
+ email: EmailStr = Field(...)
+ course: str = Field(...)
+ gpa: float = Field(..., le=4.0)
+ model_config = ConfigDict(
+ populate_by_name=True,
+ arbitrary_types_allowed=True,
+ json_schema_extra={
+ "example": {
+ "name": "Jane Doe",
+ "email": "jdoe@example.com",
+ "course": "Experiments, Science, and Fashion in Nanophotonics",
+ "gpa": 3.0,
+ }
+ },
+ )
+
+UpdateStudentModel Class
+`````````````````````````
+
+The ``UpdateStudentModel`` has the following key differences from the
+``StudentModel``:
+
+- It does not have an ``id`` attribute, as this cannot be modified
+- All fields are optional, so you can supply only the fields you want to update
+- The ``mongo_config`` variables includes ``json_encoders={ObjectId: str}``
+
+FastAPI encodes and decodes data as JSON strings, which do not support
+all the data types that MongoDB's BSON data type can store. BSON has
+support for more non-JSON-native data types, including ``ObjectId``
+which is used for the default UUID attribute, ``_id``. Because of this,
+you must convert ``ObjectId`` objects to strings before storing them in
+the ``_id`` field. The ``json_encoders`` setting instructs Pydantic to do this.
+
+For more information about how BSON compares to JSON, see the `JSON
+and BSON `__ MongoDB article.
+
+You can see the ``UpdateStudentModel`` definition in the following code in the ``app.py`` file:
+
+.. code-block:: python
+
+ class UpdateStudentModel(BaseModel):
+ """
+ A set of optional updates to be made to a document in the database.
+ """
+
+ name: Optional[str] = None
+ email: Optional[EmailStr] = None
+ course: Optional[str] = None
+ gpa: Optional[float] = None
+ model_config = ConfigDict(
+ arbitrary_types_allowed=True,
+ json_encoders={ObjectId: str},
+ json_schema_extra={
+ "example": {
+ "name": "Jane Doe",
+ "email": "jdoe@example.com",
+ "course": "Experiments, Science, and Fashion in anophotonics",
+ "gpa": 3.0,
+ }
+ },
+ )
+
+StudentCollection Class
+````````````````````````
+
+The ``StudentCollection`` class is defined to encapsulate a list of
+``StudentModel`` instances. The purpose of this endpoint is to provide some
+protection from JSON Hijacking. For more information, you can read the `JSON
+Hijacking `__
+article on the Haacked website.
+
+You can see the ``StudentCollection`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ class StudentCollection(BaseModel):
+ """
+ A container holding a list of `StudentModel` instances
+ """
+
+ students: List[StudentModel]
+
+Review the Application Routes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following table describes the application routes defined in this application:
+
+.. list-table::
+ :header-rows: 1
+ :stub-columns: 1
+ :widths: 25,75
+
+ * - Route
+ - Action
+
+ * - ``POST /students/``
+ - Create a new student
+
+ * - ``GET /students/``
+ - View a list of all students
+
+ * - ``GET /students/{id}``
+ - View a single student
+
+ * - ``PUT /students/{id}``
+ - Update a student
+
+ * - ``DELETE /students/{id}``
+ - Delete a student
+
+These routes are defined in the ``app.py`` file as described in the following
+sections:
+
+Student Routes
+```````````````
+
+The ``create_student`` route receives the new student data as a JSON
+string in a ``POST`` request. It decodes the JSON request body into a
+Python dictionary, then passes it to your MongoDB client.
+
+The ``insert_one`` method response includes the ``_id`` of the newly
+created student, provided as ``id`` because this endpoint specifies
+``response_model_by_alias=False`` in the ``post`` decorator call. After
+inserting the new student, the method uses the ``inserted_id`` to find the
+correct document and return this in the ``JSONResponse``.
+
+FastAPI returns an HTTP ``200`` status code by default, but this route returns
+a ``201`` ("Created") to explicitly indicate that the student was created.
+
+You can see the ``create_student`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ @app.post(
+ "/students/",
+ response_description="Add new student",
+ response_model=StudentModel,
+ status_code=status.HTTP_201_CREATED,
+ response_model_by_alias=False,
+ )
+ async def create_student(student: StudentModel = Body(...)):
+ """
+ Insert a new student record.
+
+ A unique ``id`` will be created and provided in the response.
+ """
+ new_student = await student_collection.insert_one(
+ student.model_dump(by_alias=True, exclude=["id"])
+ )
+ created_student = await student_collection.find_one(
+ {"_id": new_student.inserted_id}
+ )
+ return created_student
+
+Read Routes
+```````````
+
+The application has one route for viewing all students, and one for viewing an
+individual student, specified by their ``id``.
+
+You can see the ``list_students`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ @app.get(
+ "/students/",
+ response_description="List all students",
+ response_model=StudentCollection,
+ response_model_by_alias=False,
+ )
+ async def list_students():
+ """
+ List all the student data in the database.
+
+ The response is unpaginated and limited to 1000 results.
+ """
+
+ return StudentCollection(students=await student_collection.find().to_list(1000))
+
+.. note:: Results Pagination
+
+ This example uses the ``to_list()`` method; but in a real application,
+ we recommend using the `skip and limit parameters
+ <{+api-root+}pymongo/asynchronous/collection.html#pymongo.asynchronous.collection.AsyncCollection.find>`__
+ in ``find`` to paginate your results.
+
+The student detail route has a path parameter of ``id``, which FastAPI
+passes as an argument to the ``show_student`` function. It uses the ``id``
+to attempt to find the corresponding student in the database.
+
+If a document with the specified ``id`` does not exist, then it raises an
+``HTTPException`` with a status of ``404``.
+
+You can see the ``show_students`` definition in the following code in the ``app.py`` file:
+
+.. code-block:: python
+
+ @app.get(
+ "/students/{id}",
+ response_description="Get a single student",
+ response_model=StudentModel,
+ response_model_by_alias=False,
+ )
+ async def show_student(id: str):
+ """
+ Get the record for a specific student, looked up by ``id``.
+ """
+ if (
+ student := await student_collection.find_one({"_id": ObjectId(id)})
+ ) is not None:
+ return student
+
+ raise HTTPException(status_code=404, detail="Student {id} not found")
+
+Update Route
+`````````````
+
+The ``update_student`` route functions similarly to a combination of the
+``create_student`` and the ``show_student`` routes. It receives the ``id``
+of the student to update, and the new data in the JSON body.
+
+This route iterates over all the parameters in the received data and only
+modifies the parameters provided. It uses the `find_one_and_update()
+<{+api-root+}pymongo/asynchronous/collection.html#pymongo.asynchronous.collection.AsyncCollection.find_one_and_update>`__
+to :manual:`$set ` method.
+
+If there are no fields to update, then it returns the original ``StudentModel``
+document. If it cannot find a matching document to update or return, then it
+raises a ``404`` error.
+
+You can see the ``update_student`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ @app.put(
+ "/students/{id}",
+ response_description="Update a student",
+ response_model=StudentModel,
+ response_model_by_alias=False,
+ )
+ async def update_student(id: str, student: UpdateStudentModel = Body(...)):
+ """
+ Update individual fields of an existing student record.
+
+ Only the provided fields will be updated.
+ Any missing or `null` fields will be ignored.
+ """
+ student = {
+ k: v for k, v in student.model_dump(by_alias=True).items() if v is not None
+ }
+
+ if len(student) >= 1:
+ update_result = await student_collection.find_one_and_update(
+ {"_id": ObjectId(id)},
+ {"$set": student},
+ return_document=ReturnDocument.AFTER,
+ )
+ if update_result is not None:
+ return update_result
+ else:
+ raise HTTPException(status_code=404, detail=f"Student {id} not found")
+
+ # The update is empty, so return the matching document:
+ if (existing_student := await student_collection.find_one({"_id": ObjectId(id)})) is not None:
+ return existing_student
+
+ raise HTTPException(status_code=404, detail=f"Student {id} not found")
+
+Delete Route
+`````````````
+
+The ``delete_student`` acts on a single document, so you must supply
+an ``id`` in the URL. If it finds a matching document and successfully
+deletes it, then it returns an HTTP status of ``204`` ("No Content") and
+does not return a document. If it cannot find a student with the
+specified ``id``, then it returns a ``404`` error.
+
+You can see the ``delete_student`` definition in the following code in the
+``app.py`` file:
+
+.. code-block:: python
+
+ @app.delete("/students/{id}", response_description="Delete a student")
+ async def delete_student(id: str):
+ """
+ Remove a single student record from the database.
+ """
+ delete_result = await student_collection.delete_one({"_id": ObjectId(id)})
+
+ if delete_result.deleted_count == 1:
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
+
+ raise HTTPException(status_code=404, detail=f"Student {id} not found")
+
+Test the API
+~~~~~~~~~~~~~
+
+Now that you understand how the application works, you can start using your
+endpoints. Use the following steps to send requests to your endpoints and see
+the results:
+
+.. procedure::
+ :style: connected
+
+ .. step:: Start your FastAPI server
+
+ Run the following code in your terminal to start your FastAPI server:
+
+ .. code-block:: shell
+
+ uvicorn app:app --reload
+
+ .. step:: Send requests
+
+ Navigate to ``__. The following image
+ shows interface generated by the FastAPI server.
+
+ .. image:: /includes/integrations/fastapi-browser.png
+ :alt: Screenshot of browser and swagger UI
+
+ Click on the endpoint you want to send a request to, fill in any
+ necessary parameters, and click :guilabel:`Execute`.
+ In the :guilabel:`Show Student` endpoint, you can see the sample data
+ defined in the ``StudentModel.model_config`` variable.
+
+ .. tip:: Use curl to test your API
+
+ If you prefer using the command line, you can send a request by using
+ the ``curl`` command, such as this one:
+
+ .. code-block:: shell
+
+ curl -X POST "http://127.0.0.1:8000/students/" \
+ -H "Content-Type: application/json" \
+ -d '{"name": "Jane Doe", "email": "jdoe@example.com", "course": "Physics", "gpa": 3.8}'
+
+ .. step:: See the results of your requests
+
+ You can see the results of successful ``POST``, ``PUT`` and ``DELETE``
+ requests by navigating to your ``college`` database in Atlas.
+ You can also see your results by using the :guilabel:`List Students` endpoint.
+
+Next Steps
+----------
+
+Now that you have a basic understanding of how FastAPI integrates with MongoDB
+and the {+driver-async+} driver, you can add more features. The following list
+includes some suggestions for how you could add to this application:
+
+- `Additional data validation `__
+- `Dependency injection `__
+- `Testing with TestClient `__
+- `Error handling and custom responses `__
+- `Authentication and authorization `__
+
+More Resources
+--------------
+
+For more information about FastAPI integration, see the following resources:
+
+- MongoDB's Full Stack FastAPI App Generator
+ - `Blog post: Introducing the Full Stack FastAPI App Generator for Python
+ Developers
+ `__
+ - :github:`Github repository: full-stack-fastapi-mongodb `
+- `Introducing the FARM stack (FastAPI, React and MongoDB) blog post
+ `__
+- `FastAPI documentation `__
+- :github:`Third-party FastAPI Integration Options `
+
+For support or to contribute to the MongoDB Community, see the `MongoDB
+Developer Community `__.
\ No newline at end of file