<h3 dir="auto">Description</h3>
<p dir="auto">In this issue i'd like to gather all the information about the use of MongoDB, FastApi and Pydantic. At this point this is a "rather complete" solution, but i'd like to gather feedback and comments from the community to se how it can be improved.</p>
<p dir="auto">The biggest pain point that started this and several other threads when trying to use FastAPI with mongo is the <code class="notranslate">_id</code> field. There are several issues here:</p>
<ol dir="auto">
<li>Most known one - <code class="notranslate">_id</code> field being <code class="notranslate">ObjectId</code>, which is not very JSON-friendly</li>
<li><code class="notranslate">_id</code> field by it's naming is not very python-friendly (that is, written as is in Pydantic model, it would become a private field - many IDEs will point that)</li>
</ol>
<p dir="auto">Below i'll try to describe solutions i've found in different places and see what cases do the cover and what's left unsolved.</p>
<p dir="auto">Let's say, we have some Joe, who's a regular developer. Joe just discovered FastAPI and is familiar with mongo (to the extend that he can create and fetch documents from DB). Joe wants to build clean and fast api that would:</p>
<p dir="auto"><g-emoji class="g-emoji" alias="one" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0031-20e3.png">1️⃣</g-emoji> Be able to define mongo-compatible documents as regular Pydantic models (with all the proper validations in place):</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class User(BaseModel):
    id: ObjectId = Field(description=&quot;User id&quot;)
    name: str = Field()"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s1">id</span>: <span class="pl-v">ObjectId</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"User id"</span>)
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()</pre></div>
<p dir="auto"><g-emoji class="g-emoji" alias="two" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0032-20e3.png">2️⃣</g-emoji> Write routes that would use native Pydantic models as usual:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@app.post('/me', response_model=User)
def save_me(body: User):
   ..."><pre class="notranslate"><span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
   ...</pre></div>
<p dir="auto"><g-emoji class="g-emoji" alias="three" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0033-20e3.png">3️⃣</g-emoji> Have api to return json like <code class="notranslate">{"id": "5ed8b7eaccda20c1d4e95bb0", "name": "Joe"}</code> (it's quite expected in the "outer world" to have <code class="notranslate">id</code> field for the document rather than <code class="notranslate">_id</code>. And it just looks nicer.)<br>
<g-emoji class="g-emoji" alias="four" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0034-20e3.png">4️⃣</g-emoji> Have Swagger and ReDoc documentation to display fields <code class="notranslate">id</code> (str), <code class="notranslate">name</code> (str)<br>
<g-emoji class="g-emoji" alias="five" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0035-20e3.png">5️⃣</g-emoji> Be able to save Pydantic documents into Mongo with proper <code class="notranslate">id</code> field substitution:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="user = User(id=ObjectId(), name='Joe')
inserted = db.user.insert_one(user) # This should insert document as `{&quot;_id&quot;: user.id, &quot;name&quot;: &quot;Joe&quot;}`
assert inserted.inserted_id == user.id"><pre class="notranslate"><span class="pl-s1">user</span> <span class="pl-c1">=</span> <span class="pl-v">User</span>(<span class="pl-s1">id</span><span class="pl-c1">=</span><span class="pl-v">ObjectId</span>(), <span class="pl-s1">name</span><span class="pl-c1">=</span><span class="pl-s">'Joe'</span>)
<span class="pl-s1">inserted</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-s1">user</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">user</span>) <span class="pl-c"># This should insert document as `{"_id": user.id, "name": "Joe"}`</span>
<span class="pl-k">assert</span> <span class="pl-s1">inserted</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">user</span>.<span class="pl-s1">id</span></pre></div>
<p dir="auto"><g-emoji class="g-emoji" alias="six" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0036-20e3.png">6️⃣</g-emoji> Should be able to fetch documents from Mongo with proper <code class="notranslate">id</code> matching:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="user_id = ObjectId()
found = db.user.find({&quot;_id&quot;: user_id})
user = User(**found)
assert user.id == user_id"><pre class="notranslate"><span class="pl-s1">user_id</span> <span class="pl-c1">=</span> <span class="pl-v">ObjectId</span>()
<span class="pl-s1">found</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-s1">user</span>.<span class="pl-en">find</span>({<span class="pl-s">"_id"</span>: <span class="pl-s1">user_id</span>})
<span class="pl-s1">user</span> <span class="pl-c1">=</span> <span class="pl-v">User</span>(<span class="pl-c1">**</span><span class="pl-s1">found</span>)
<span class="pl-k">assert</span> <span class="pl-s1">user</span>.<span class="pl-s1">id</span> <span class="pl-c1">==</span> <span class="pl-s1">user_id</span></pre></div>
<h3 dir="auto">Known solutions</h3>
<h4 dir="auto">Validating ObjectId</h4>
<p dir="auto">As proposed in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="483398775" data-permission-text="Title is private" data-url="https://github.com/tiangolo/fastapi/issues/452" data-hovercard-type="issue" data-hovercard-url="/tiangolo/fastapi/issues/452/hovercard" href="https://github.com/tiangolo/fastapi/issues/452">#452</a>, one can define custom field for <code class="notranslate">ObjectId</code> and apply validations to it. One can also create base model that would encode <code class="notranslate">ObjectId</code> into strings:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="
class OID(str):
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        try:
            return ObjectId(str(v))
        except InvalidId:
            raise ValueError(&quot;Not a valid ObjectId&quot;)


class MongoModel(BaseModel):
    class Config(BaseConfig):
        json_encoders = {
            datetime: lambda dt: dt.isoformat(),
            ObjectId: lambda oid: str(oid),
        }

class User(MongoModel):
    id: OID = Field()
    name: str = Field()


@app.post('/me', response_model=User)
def save_me(body: User):
    assert isinstance(body.id, ObjectId)
    return body"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">OID</span>(<span class="pl-s1">str</span>):
    <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
    <span class="pl-k">def</span> <span class="pl-en">__get_validators__</span>(<span class="pl-s1">cls</span>):
        <span class="pl-k">yield</span> <span class="pl-s1">cls</span>.<span class="pl-s1">validate</span>

    <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
    <span class="pl-k">def</span> <span class="pl-en">validate</span>(<span class="pl-s1">cls</span>, <span class="pl-s1">v</span>):
        <span class="pl-k">try</span>:
            <span class="pl-k">return</span> <span class="pl-v">ObjectId</span>(<span class="pl-en">str</span>(<span class="pl-s1">v</span>))
        <span class="pl-k">except</span> <span class="pl-v">InvalidId</span>:
            <span class="pl-k">raise</span> <span class="pl-v">ValueError</span>(<span class="pl-s">"Not a valid ObjectId"</span>)


<span class="pl-k">class</span> <span class="pl-v">MongoModel</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-k">class</span> <span class="pl-v">Config</span>(<span class="pl-v">BaseConfig</span>):
        <span class="pl-s1">json_encoders</span> <span class="pl-c1">=</span> {
            <span class="pl-s1">datetime</span>: <span class="pl-k">lambda</span> <span class="pl-s1">dt</span>: <span class="pl-s1">dt</span>.<span class="pl-en">isoformat</span>(),
            <span class="pl-v">ObjectId</span>: <span class="pl-k">lambda</span> <span class="pl-s1">oid</span>: <span class="pl-en">str</span>(<span class="pl-s1">oid</span>),
        }

<span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">MongoModel</span>):
    <span class="pl-s1">id</span>: <span class="pl-v">OID</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()


<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
    <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
    <span class="pl-k">return</span> <span class="pl-s1">body</span></pre></div>
<p dir="auto">Now we have:</p>
<table role="table">
<thead>
<tr>
<th>1️⃣</th>
<th>2️⃣</th>
<th><g-emoji class="g-emoji" alias="three" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0033-20e3.png">3️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="four" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0034-20e3.png">4️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="five" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0035-20e3.png">5️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="six" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0036-20e3.png">6️⃣</g-emoji></th>
</tr>
</thead>
<tbody>
<tr>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td>✅</td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="ballot_box_with_check" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2611.png">☑️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="ballot_box_with_check" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2611.png">☑️</g-emoji></td>
</tr>
</tbody>
</table>
<h4 dir="auto">Dealing with <code class="notranslate">_id</code></h4>
<p dir="auto">Another suggested option would be to use <code class="notranslate">alias="_id"</code> on Pydantic model:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class MongoModel(BaseModel):
    class Config(BaseConfig):
        allow_population_by_field_name = True  # &lt;&lt; Added
        json_encoders = {
            datetime: lambda dt: dt.isoformat(),
            ObjectId: lambda oid: str(oid),
        }

class User(MongoModel):
    id: OID = Field(alias=&quot;_id&quot;)  # &lt;&lt; Notice alias
    name: str = Field()


@app.post('/me', response_model=User)
def save_me(body: User):
    assert isinstance(body.id, ObjectId)
    res = db.insert_one(body.dict(by_alias=True))  # &lt;&lt; Inserting as dict with aliased fields
    assert res.inserted_id == body.id
    return body"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">MongoModel</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-k">class</span> <span class="pl-v">Config</span>(<span class="pl-v">BaseConfig</span>):
        <span class="pl-s1">allow_population_by_field_name</span> <span class="pl-c1">=</span> <span class="pl-c1">True</span>  <span class="pl-c"># &lt;&lt; Added</span>
        <span class="pl-s1">json_encoders</span> <span class="pl-c1">=</span> {
            <span class="pl-s1">datetime</span>: <span class="pl-k">lambda</span> <span class="pl-s1">dt</span>: <span class="pl-s1">dt</span>.<span class="pl-en">isoformat</span>(),
            <span class="pl-v">ObjectId</span>: <span class="pl-k">lambda</span> <span class="pl-s1">oid</span>: <span class="pl-en">str</span>(<span class="pl-s1">oid</span>),
        }

<span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">MongoModel</span>):
    <span class="pl-s1">id</span>: <span class="pl-v">OID</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">alias</span><span class="pl-c1">=</span><span class="pl-s">"_id"</span>)  <span class="pl-c"># &lt;&lt; Notice alias</span>
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()


<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
    <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">body</span>.<span class="pl-en">dict</span>(<span class="pl-s1">by_alias</span><span class="pl-c1">=</span><span class="pl-c1">True</span>))  <span class="pl-c"># &lt;&lt; Inserting as dict with aliased fields</span>
    <span class="pl-k">assert</span> <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">body</span>.<span class="pl-s1">id</span>
    <span class="pl-k">return</span> <span class="pl-s1">body</span></pre></div>
<p dir="auto">Now are able to save to DB using <code class="notranslate">User.id</code> field as <code class="notranslate">_id</code> - that solves 5️⃣.</p>
<p dir="auto">However, how Swagger and ReDoc show id field as <code class="notranslate">_id</code>, and json that is returned looks like this: <code class="notranslate">{"_id":"5ed803afba6455fd78659988","name":"Joe"}</code>. This is a regression for 3️⃣ and 4️⃣<br>
Now we have:</p>
<table role="table">
<thead>
<tr>
<th><g-emoji class="g-emoji" alias="one" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0031-20e3.png">1️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="two" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0032-20e3.png">2️⃣</g-emoji></th>
<th>3️⃣</th>
<th><g-emoji class="g-emoji" alias="four" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0034-20e3.png">4️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="five" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0035-20e3.png">5️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="six" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0036-20e3.png">6️⃣</g-emoji></th>
</tr>
</thead>
<tbody>
<tr>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="ballot_box_with_check" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2611.png">☑️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="ballot_box_with_check" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2611.png">☑️</g-emoji></td>
<td>✅️</td>
<td>☑️</td>
</tr>
</tbody>
</table>
<h4 dir="auto">Hacking our way through</h4>
<p dir="auto">We can do some extra coding to keep <code class="notranslate">id</code> field and make proper inserting into DB. Effectively, we're shuffling <code class="notranslate">id</code> and <code class="notranslate">_id</code> field in <code class="notranslate">MongoModel</code> upon dumping/loading.</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class MongoModel(BaseModel):

    class Config(BaseConfig):
        allow_population_by_field_name = True
        json_encoders = {
            datetime: lambda dt: dt.isoformat(),
            ObjectId: lambda oid: str(oid),
        }

    @classmethod
    def from_mongo(cls, data: dict):
        &quot;&quot;&quot;We must convert _id into &quot;id&quot;. &quot;&quot;&quot;
        if not data:
            return data
        id = data.pop('_id', None)
        return cls(**dict(data, id=id))

    def mongo(self, **kwargs):
        exclude_unset = kwargs.pop('exclude_unset', True)
        by_alias = kwargs.pop('by_alias', True)

        parsed = self.dict(
            exclude_unset=exclude_unset,
            by_alias=by_alias,
            **kwargs,
        )

        # Mongo uses `_id` as default key. We should stick to that as well.
        if '_id' not in parsed and 'id' in parsed:
            parsed['_id'] = parsed.pop('id')

        return parsed

@app.post('/me', response_model=User)
def save_me(body: User):
    assert isinstance(body.id, ObjectId)
    res = db.insert_one(body.mongo())  # &lt;&lt; Notice that we should use `User.mongo()` now.
    assert res.inserted_id == body.id
    return body"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">MongoModel</span>(<span class="pl-v">BaseModel</span>):

    <span class="pl-k">class</span> <span class="pl-v">Config</span>(<span class="pl-v">BaseConfig</span>):
        <span class="pl-s1">allow_population_by_field_name</span> <span class="pl-c1">=</span> <span class="pl-c1">True</span>
        <span class="pl-s1">json_encoders</span> <span class="pl-c1">=</span> {
            <span class="pl-s1">datetime</span>: <span class="pl-k">lambda</span> <span class="pl-s1">dt</span>: <span class="pl-s1">dt</span>.<span class="pl-en">isoformat</span>(),
            <span class="pl-v">ObjectId</span>: <span class="pl-k">lambda</span> <span class="pl-s1">oid</span>: <span class="pl-en">str</span>(<span class="pl-s1">oid</span>),
        }

    <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
    <span class="pl-k">def</span> <span class="pl-en">from_mongo</span>(<span class="pl-s1">cls</span>, <span class="pl-s1">data</span>: <span class="pl-s1">dict</span>):
        <span class="pl-s">"""We must convert _id into "id". """</span>
        <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-s1">data</span>:
            <span class="pl-k">return</span> <span class="pl-s1">data</span>
        <span class="pl-s1">id</span> <span class="pl-c1">=</span> <span class="pl-s1">data</span>.<span class="pl-en">pop</span>(<span class="pl-s">'_id'</span>, <span class="pl-c1">None</span>)
        <span class="pl-k">return</span> <span class="pl-en">cls</span>(<span class="pl-c1">**</span><span class="pl-en">dict</span>(<span class="pl-s1">data</span>, <span class="pl-s1">id</span><span class="pl-c1">=</span><span class="pl-s1">id</span>))

    <span class="pl-k">def</span> <span class="pl-en">mongo</span>(<span class="pl-s1">self</span>, <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>):
        <span class="pl-s1">exclude_unset</span> <span class="pl-c1">=</span> <span class="pl-s1">kwargs</span>.<span class="pl-en">pop</span>(<span class="pl-s">'exclude_unset'</span>, <span class="pl-c1">True</span>)
        <span class="pl-s1">by_alias</span> <span class="pl-c1">=</span> <span class="pl-s1">kwargs</span>.<span class="pl-en">pop</span>(<span class="pl-s">'by_alias'</span>, <span class="pl-c1">True</span>)

        <span class="pl-s1">parsed</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-en">dict</span>(
            <span class="pl-s1">exclude_unset</span><span class="pl-c1">=</span><span class="pl-s1">exclude_unset</span>,
            <span class="pl-s1">by_alias</span><span class="pl-c1">=</span><span class="pl-s1">by_alias</span>,
            <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>,
        )

        <span class="pl-c"># Mongo uses `_id` as default key. We should stick to that as well.</span>
        <span class="pl-k">if</span> <span class="pl-s">'_id'</span> <span class="pl-c1">not</span> <span class="pl-c1">in</span> <span class="pl-s1">parsed</span> <span class="pl-c1">and</span> <span class="pl-s">'id'</span> <span class="pl-c1">in</span> <span class="pl-s1">parsed</span>:
            <span class="pl-s1">parsed</span>[<span class="pl-s">'_id'</span>] <span class="pl-c1">=</span> <span class="pl-s1">parsed</span>.<span class="pl-en">pop</span>(<span class="pl-s">'id'</span>)

        <span class="pl-k">return</span> <span class="pl-s1">parsed</span>

<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
    <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">body</span>.<span class="pl-en">mongo</span>())  <span class="pl-c"># &lt;&lt; Notice that we should use `User.mongo()` now.</span>
    <span class="pl-k">assert</span> <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">body</span>.<span class="pl-s1">id</span>
    <span class="pl-k">return</span> <span class="pl-s1">body</span></pre></div>
<p dir="auto">This brings back documentation and proper output and solves the insertion:</p>
<table role="table">
<thead>
<tr>
<th>1️⃣</th>
<th>2️⃣</th>
<th><g-emoji class="g-emoji" alias="three" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0033-20e3.png">3️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="four" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0034-20e3.png">4️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="five" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0035-20e3.png">5️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="six" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0036-20e3.png">6️⃣</g-emoji></th>
</tr>
</thead>
<tbody>
<tr>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
<td>✅️</td>
<td><g-emoji class="g-emoji" alias="ballot_box_with_check" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2611.png">☑️</g-emoji></td>
</tr>
</tbody>
</table>
<p dir="auto">Looks like we're getting closer...</p>
<h4 dir="auto">Fetching docs from DB</h4>
<p dir="auto">Now, let's try to fetch doc from DB and return it:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@app.post('/me', response_model=User)
def save_me(body: User):
    assert isinstance(body.id, ObjectId)
    res = db.insert_one(body.mongo())  # &lt;&lt; Notice that we should use `User.mongo()` now.
    assert res.inserted_id == body.id

    found = col.find_one({'_id': res.inserted_id})
    return found

    &quot;&quot;&quot;
    pydantic.error_wrappers.ValidationError: 1 validation error for User
    response -&gt; id
      field required (type=value_error.missing)
    &quot;&quot;&quot;"><pre class="notranslate"><span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
    <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">body</span>.<span class="pl-en">mongo</span>())  <span class="pl-c"># &lt;&lt; Notice that we should use `User.mongo()` now.</span>
    <span class="pl-k">assert</span> <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">body</span>.<span class="pl-s1">id</span>

    <span class="pl-s1">found</span> <span class="pl-c1">=</span> <span class="pl-s1">col</span>.<span class="pl-en">find_one</span>({<span class="pl-s">'_id'</span>: <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span>})
    <span class="pl-k">return</span> <span class="pl-s1">found</span>

    <span class="pl-s">"""</span>
<span class="pl-s">    pydantic.error_wrappers.ValidationError: 1 validation error for User</span>
<span class="pl-s">    response -&gt; id</span>
<span class="pl-s">      field required (type=value_error.missing)</span>
<span class="pl-s">    """</span></pre></div>
<p dir="auto">The workaround for this is to use <code class="notranslate">User.from_mongo</code>:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@app.post('/me', response_model=User)
def save_me(body: User):
    assert isinstance(body.id, ObjectId)
    res = db.insert_one(body.mongo())
    assert res.inserted_id == body.id

    found = col.find_one({'_id': res.inserted_id})
    return User.from_mongo(found)  # &lt;&lt; Notice that we should use `User.from_mongo()` now."><pre class="notranslate"><span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
    <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">body</span>.<span class="pl-en">mongo</span>())
    <span class="pl-k">assert</span> <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">body</span>.<span class="pl-s1">id</span>

    <span class="pl-s1">found</span> <span class="pl-c1">=</span> <span class="pl-s1">col</span>.<span class="pl-en">find_one</span>({<span class="pl-s">'_id'</span>: <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span>})
    <span class="pl-k">return</span> <span class="pl-v">User</span>.<span class="pl-en">from_mongo</span>(<span class="pl-s1">found</span>)  <span class="pl-c"># &lt;&lt; Notice that we should use `User.from_mongo()` now.</span></pre></div>
<p dir="auto">This seem to cover fetching from DB. Now we have:</p>
<table role="table">
<thead>
<tr>
<th>1️⃣</th>
<th><g-emoji class="g-emoji" alias="two" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0032-20e3.png">2️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="three" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0033-20e3.png">3️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="four" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0034-20e3.png">4️⃣</g-emoji></th>
<th><g-emoji class="g-emoji" alias="five" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/0035-20e3.png">5️⃣</g-emoji></th>
<th>6️⃣</th>
</tr>
</thead>
<tbody>
<tr>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
<td><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅️</g-emoji></td>
</tr>
</tbody>
</table>
<h3 dir="auto">Conclusion and questions</h3>
<p dir="auto">Under the spoiler one can find final code to make FastApi work with mongo in the most "native" way:</p>
<details>
  <summary>Full code</summary>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class OID(str):
  @classmethod
  def __get_validators__(cls):
      yield cls.validate

  @classmethod
  def validate(cls, v):
      try:
          return ObjectId(str(v))
      except InvalidId:
          raise ValueError(&quot;Not a valid ObjectId&quot;)


class MongoModel(BaseModel):

  class Config(BaseConfig):
      allow_population_by_field_name = True
      json_encoders = {
          datetime: lambda dt: dt.isoformat(),
          ObjectId: lambda oid: str(oid),
      }

  @classmethod
  def from_mongo(cls, data: dict):
      &quot;&quot;&quot;We must convert _id into &quot;id&quot;. &quot;&quot;&quot;
      if not data:
          return data
      id = data.pop('_id', None)
      return cls(**dict(data, id=id))

  def mongo(self, **kwargs):
      exclude_unset = kwargs.pop('exclude_unset', True)
      by_alias = kwargs.pop('by_alias', True)

      parsed = self.dict(
          exclude_unset=exclude_unset,
          by_alias=by_alias,
          **kwargs,
      )

      # Mongo uses `_id` as default key. We should stick to that as well.
      if '_id' not in parsed and 'id' in parsed:
          parsed['_id'] = parsed.pop('id')

      return parsed


class User(MongoModel):
  id: OID = Field()
  name: str = Field()


@app.post('/me', response_model=User)
def save_me(body: User):
  assert isinstance(body.id, ObjectId)
  res = db.insert_one(body.mongo())
  assert res.inserted_id == body.id

  found = col.find_one({'_id': res.inserted_id})
  return User.from_mongo(found)"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">OID</span>(<span class="pl-s1">str</span>):
  <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
  <span class="pl-k">def</span> <span class="pl-en">__get_validators__</span>(<span class="pl-s1">cls</span>):
      <span class="pl-k">yield</span> <span class="pl-s1">cls</span>.<span class="pl-s1">validate</span>

  <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
  <span class="pl-k">def</span> <span class="pl-en">validate</span>(<span class="pl-s1">cls</span>, <span class="pl-s1">v</span>):
      <span class="pl-k">try</span>:
          <span class="pl-k">return</span> <span class="pl-v">ObjectId</span>(<span class="pl-en">str</span>(<span class="pl-s1">v</span>))
      <span class="pl-k">except</span> <span class="pl-v">InvalidId</span>:
          <span class="pl-k">raise</span> <span class="pl-v">ValueError</span>(<span class="pl-s">"Not a valid ObjectId"</span>)


<span class="pl-k">class</span> <span class="pl-v">MongoModel</span>(<span class="pl-v">BaseModel</span>):

  <span class="pl-k">class</span> <span class="pl-v">Config</span>(<span class="pl-v">BaseConfig</span>):
      <span class="pl-s1">allow_population_by_field_name</span> <span class="pl-c1">=</span> <span class="pl-c1">True</span>
      <span class="pl-s1">json_encoders</span> <span class="pl-c1">=</span> {
          <span class="pl-s1">datetime</span>: <span class="pl-k">lambda</span> <span class="pl-s1">dt</span>: <span class="pl-s1">dt</span>.<span class="pl-en">isoformat</span>(),
          <span class="pl-v">ObjectId</span>: <span class="pl-k">lambda</span> <span class="pl-s1">oid</span>: <span class="pl-en">str</span>(<span class="pl-s1">oid</span>),
      }

  <span class="pl-en">@<span class="pl-s1">classmethod</span></span>
  <span class="pl-k">def</span> <span class="pl-en">from_mongo</span>(<span class="pl-s1">cls</span>, <span class="pl-s1">data</span>: <span class="pl-s1">dict</span>):
      <span class="pl-s">"""We must convert _id into "id". """</span>
      <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-s1">data</span>:
          <span class="pl-k">return</span> <span class="pl-s1">data</span>
      <span class="pl-s1">id</span> <span class="pl-c1">=</span> <span class="pl-s1">data</span>.<span class="pl-en">pop</span>(<span class="pl-s">'_id'</span>, <span class="pl-c1">None</span>)
      <span class="pl-k">return</span> <span class="pl-en">cls</span>(<span class="pl-c1">**</span><span class="pl-en">dict</span>(<span class="pl-s1">data</span>, <span class="pl-s1">id</span><span class="pl-c1">=</span><span class="pl-s1">id</span>))

  <span class="pl-k">def</span> <span class="pl-en">mongo</span>(<span class="pl-s1">self</span>, <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>):
      <span class="pl-s1">exclude_unset</span> <span class="pl-c1">=</span> <span class="pl-s1">kwargs</span>.<span class="pl-en">pop</span>(<span class="pl-s">'exclude_unset'</span>, <span class="pl-c1">True</span>)
      <span class="pl-s1">by_alias</span> <span class="pl-c1">=</span> <span class="pl-s1">kwargs</span>.<span class="pl-en">pop</span>(<span class="pl-s">'by_alias'</span>, <span class="pl-c1">True</span>)

      <span class="pl-s1">parsed</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-en">dict</span>(
          <span class="pl-s1">exclude_unset</span><span class="pl-c1">=</span><span class="pl-s1">exclude_unset</span>,
          <span class="pl-s1">by_alias</span><span class="pl-c1">=</span><span class="pl-s1">by_alias</span>,
          <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>,
      )

      <span class="pl-c"># Mongo uses `_id` as default key. We should stick to that as well.</span>
      <span class="pl-k">if</span> <span class="pl-s">'_id'</span> <span class="pl-c1">not</span> <span class="pl-c1">in</span> <span class="pl-s1">parsed</span> <span class="pl-c1">and</span> <span class="pl-s">'id'</span> <span class="pl-c1">in</span> <span class="pl-s1">parsed</span>:
          <span class="pl-s1">parsed</span>[<span class="pl-s">'_id'</span>] <span class="pl-c1">=</span> <span class="pl-s1">parsed</span>.<span class="pl-en">pop</span>(<span class="pl-s">'id'</span>)

      <span class="pl-k">return</span> <span class="pl-s1">parsed</span>


<span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">MongoModel</span>):
  <span class="pl-s1">id</span>: <span class="pl-v">OID</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()
  <span class="pl-s1">name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>()


<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">'/me'</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">User</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">save_me</span>(<span class="pl-s1">body</span>: <span class="pl-v">User</span>):
  <span class="pl-k">assert</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">body</span>.<span class="pl-s1">id</span>, <span class="pl-v">ObjectId</span>)
  <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">db</span>.<span class="pl-en">insert_one</span>(<span class="pl-s1">body</span>.<span class="pl-en">mongo</span>())
  <span class="pl-k">assert</span> <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span> <span class="pl-c1">==</span> <span class="pl-s1">body</span>.<span class="pl-s1">id</span>

  <span class="pl-s1">found</span> <span class="pl-c1">=</span> <span class="pl-s1">col</span>.<span class="pl-en">find_one</span>({<span class="pl-s">'_id'</span>: <span class="pl-s1">res</span>.<span class="pl-s1">inserted_id</span>})
  <span class="pl-k">return</span> <span class="pl-v">User</span>.<span class="pl-en">from_mongo</span>(<span class="pl-s1">found</span>)</pre></div>
</details>
<p dir="auto">And the list of things that are sub-optimal with given code:</p>
<ol dir="auto">
<li>One can no longer return any data and expect FastApi to apply <code class="notranslate">response_model</code> validation. Have to use <code class="notranslate">User.from_mongo</code> with every return. This is somewhat a code duplication. Would be nice to get rid of this somehow</li>
<li>The amount of "boilerplate" code needed to make FastAPI work "natively" with mongo is quite significant and it's not that straightforward. This can lead to potential errors and raises entry bar for someone who wants to start using FastAPI with mongo</li>
<li>There is still this duality, where in models one uses <code class="notranslate">id</code> field, while all mongo queries are built using <code class="notranslate">_id</code>. Afraid there is no way to get rid of this though... (I'm aware that MongoEngine and other ODM engines cover this, but specifically decided to stay out of this subject and focus on "native" code)</li>
</ol>
<p dir="auto">I want to access Api level dependency of cache in custom request class. Api is defined using router and custom request middleware class is defined APIRoute.</p>
<p dir="auto">APIRoute class</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from typing import Callable

from fastapi import Request, Response, Depends
from fastapi.routing import APIRoute

class RecordRequestResponseRoute(APIRoute):
    def get_route_handler(self) -&gt; Callable:
    original_route_handler = super().get_route_handler()

    async def custom_route_handler(request: Request)\
            -&gt; Response:
        
        # need to access cache here 
        response: Response = await original_route_handler(request)
        return response

    return custom_route_handler"><pre class="notranslate"><code class="notranslate">from typing import Callable

from fastapi import Request, Response, Depends
from fastapi.routing import APIRoute

class RecordRequestResponseRoute(APIRoute):
    def get_route_handler(self) -&gt; Callable:
    original_route_handler = super().get_route_handler()

    async def custom_route_handler(request: Request)\
            -&gt; Response:
        
        # need to access cache here 
        response: Response = await original_route_handler(request)
        return response

    return custom_route_handler
</code></pre></div>
<p dir="auto">Router Api</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from fastapi import Response, Depends, APIRouter, BackgroundTasks
import fastapi_plugins
import aioredis

router = APIRouter(route_class=RecordRequestResponseRoute)


@router.get(&quot;/users&quot;, tags=[&quot;users&quot;])
async def match_frame(background_tasks: BackgroundTasks,
                  cache: aioredis.Redis = Depends(fastapi_plugins.depends_redis)):
     return {&quot;success&quot;: True, &quot;data&quot;: []}"><pre class="notranslate"><code class="notranslate">from fastapi import Response, Depends, APIRouter, BackgroundTasks
import fastapi_plugins
import aioredis

router = APIRouter(route_class=RecordRequestResponseRoute)


@router.get("/users", tags=["users"])
async def match_frame(background_tasks: BackgroundTasks,
                  cache: aioredis.Redis = Depends(fastapi_plugins.depends_redis)):
     return {"success": True, "data": []}
</code></pre></div>
<p dir="auto">I need to access cache in RecordRequestResponseRoute class. I tried using sub dependencies but that did not help. What will be the correct way to do it?</p>