Django 3
MongoDB
Motor
AsyncIO
Python

Using Motor or any other async MongoDB driver in Django 3 projects

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

You can use Motor in a Django 3 project, but not as a drop-in replacement for Django's ORM. Django 3 introduced async-capable views and middleware paths, yet the framework still assumes its own relational ORM model, so the practical pattern is to treat Motor as a separate async data-access layer that you call deliberately from async code.

Understand the boundary first

Django 3 is not an async-everything framework. It supports asynchronous request handling in some parts of the stack, but much of the ecosystem, especially the ORM, remains synchronous. MongoDB through Motor sits outside that ORM entirely.

That means two important things:

  • Motor will not behave like a native Django database backend
  • you need to manage connection setup, queries, and model mapping yourself

Once you accept that boundary, the integration becomes much easier to reason about.

Create a shared Motor client module

A common pattern is to create a small module that initializes the async client once and exposes collections or helper functions.

python
1# mongo.py
2from motor.motor_asyncio import AsyncIOMotorClient
3
4client = AsyncIOMotorClient("mongodb://localhost:27017")
5db = client["myapp"]
6users = db["users"]

Then call that from an async view.

python
1# views.py
2from django.http import JsonResponse
3from .mongo import users
4
5async def user_detail(request, user_id: str):
6    document = await users.find_one({"_id": user_id})
7    if document is None:
8        return JsonResponse({"error": "not found"}, status=404)
9    document["_id"] = str(document["_id"])
10    return JsonResponse(document)

This works because the view is async and the MongoDB driver is async too.

Keep synchronous and asynchronous code paths explicit

If a view is synchronous, calling Motor directly is awkward because the driver expects an event loop. In that situation, either keep the view async or isolate MongoDB work in a service layer that is intentionally called from async code.

Trying to mix sync Django code and async Motor calls casually usually leads to brittle wrappers and confusing execution flow.

Design your own repository layer

Because Django models and querysets do not map onto Motor, it helps to create explicit repository functions for MongoDB operations.

python
1# repositories/user_repo.py
2from .mongo import users
3
4async def get_active_users(limit: int = 10):
5    cursor = users.find({"active": True}).sort("email", 1).limit(limit)
6    return [doc async for doc in cursor]

That layer keeps raw driver calls out of views and makes later testing or replacement easier.

Treat serialization as your responsibility

MongoDB documents often contain values such as ObjectId and datetime that are not JSON-serializable by default. Django's ORM normally hides these problems for relational queries. With Motor, they are now your responsibility.

So part of a clean integration is deciding how documents become application DTOs or JSON responses. That transformation layer is not optional in a serious project.

Use MongoDB for the right reason

Many Django teams asking this question are really deciding between architectures. If the application still wants classic Django admin, model forms, relational joins, and ORM-heavy workflows, MongoDB may be an awkward fit even if Motor works technically.

Motor makes sense when the project has a genuine document-database use case and the team is willing to own the data-access layer directly.

Testing and lifecycle concerns

Motor clients are long-lived objects. Create them centrally, reuse them, and close them intentionally in tests or shutdown hooks when needed. Scattering AsyncIOMotorClient creation across request handlers is a bad idea because it turns connection management into an incidental bug source.

Also remember that Django's test conveniences are built around the ORM. For Motor-based code, you need your own database fixtures and cleanup strategy.

Common Pitfalls

  • Expecting Motor to plug into Django as if it were a normal DATABASES backend.
  • Calling Motor from synchronous Django code without a clear async boundary.
  • Returning raw MongoDB documents directly without handling types such as ObjectId.
  • Spreading database logic across views instead of creating a small repository layer.
  • Choosing MongoDB in a Django app without checking whether the application really needs a document database.

Summary

  • Motor can be used in Django 3, but it is a separate async data layer, not a Django ORM replacement.
  • Use it from async views or other deliberate async boundaries.
  • Create a shared client and repository layer instead of scattering raw driver calls.
  • Handle MongoDB document serialization explicitly.
  • Choose this architecture only if MongoDB's document model is actually a good fit for the project.

Course illustration
Course illustration

All Rights Reserved.