In February 2025, MongoDB announced something the Python community had been waiting for: an official Django MongoDB backend. No more hacky workarounds, no more third-party transpilers with limited support - just native, first-class MongoDB integration with Django's beloved ORM.
Why Django + MongoDB?
Django has long been the go-to framework for Python developers who want to build web applications quickly without sacrificing quality. Its "batteries included" philosophy, robust ORM, and automatic admin interface have made it a favorite for projects of all sizes. MongoDB, on the other hand, has emerged as the leading NoSQL database, offering:
- Flexible Schema Design: Store documents without rigid table structures.
- Horizontal Scalability: Scale out across commodity hardware.
- High Availability: Built-in replication and automatic failover.
- Developer Friendliness: The document model naturally mirrors how developers think about data in code.
When combined, Django and MongoDB offer a compelling stack:
- Rapid Development: Django's conventions combined with MongoDB's flexibility mean you spend less time on boilerplate and more time building features.
- Schema Evolution: Modify data structures without painful migrations. MongoDB's flexible schema lets your data model evolve with your application.
- Performance: MongoDB's indexing capabilities and powerful aggregation pipeline deliver excellent query performance.
- Full-Stack Django: Keep using everything you love about Django: admin, auth, forms, and templates all work seamlessly.
The Evolution: From PyMongo to Official Backend
Before the official backend, developers had three primary options for connecting Django to MongoDB:
1. PyMongo (Direct Driver)
The official Python driver for MongoDB. While powerful, it bypassed Django's ORM entirely:
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client['mydb']
collection = db['users']
# No Django ORM benefits
result = collection.find_one({'email': 'user@example.com'})
- Pros: Full MongoDB feature access, official support
- Cons: No Django ORM integration, manual everything
2. MongoEngine
A document-object mapper that provides an ORM-like experience:
from mongoengine import Document, StringField
class User(Document):
email = StringField(required=True)
name = StringField(max_length=100)
- Pros: ORM-like syntax, good abstraction
- Cons: Not Django's native ORM, compatibility issues with Django features
3. Djongo
A SQL-to-MongoDB transpiler that attempted to make MongoDB work with Django's ORM:
# settings.py
DATABASES = {
'default': {
'ENGINE': 'djongo',
'NAME': 'mydb',
}
}
- Pros: Uses Django's native ORM syntax
- Cons: Performance overhead from SQL translation, limited feature support, maintenance concerns
The Official Backend: A Game Changer
Enter django-mongodb-backend-MongoDB's official solution that provides:
- Native Django ORM support without SQL translation.
- Official MongoDB maintenance and support.
- Full Django ecosystem compatibility (admin, auth, migrations).
- A modern aggregation pipeline instead of SQL-style queries.
- Active development with a clear roadmap.
Getting Started
1. Prerequisites
Before you begin, ensure you have:
- Python 3.10 or higher.
- Django 5.0+ (version must match the backend version).
- MongoDB 5.0+ (or a MongoDB Atlas account).
2. Installation
Install the package matching your Django version:
For Django 5.2
pip install django-mongodb-backend==5.2.*
For Django 5.1
pip install django-mongodb-backend==5.1.*
For Django 5.0
pip install django-mongodb-backend==5.0.*
3. Creating a New Project
MongoDB provides an official project template that comes pre-configured:
Create project using the official template
django-admin startproject myproject \
--template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.2.x.zip
cd myproject
This template includes:
- Pre-configured settings.py for MongoDB.
- The proper project structure.
- Example configurations.
4. Existing Project Migration
For existing Django projects, update your settings.py:
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": "mongodb://localhost:27017",
"NAME": "your_database_name",
},
}
Configuration Deep Dive
1. Basic Configuration
The minimal configuration requires three settings:
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": "mongodb://localhost:27017",
"NAME": "myapp_db",
},
}
2. MongoDB Atlas Configuration
For cloud deployments with MongoDB Atlas:
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": "mongodb+srv://username:password@cluster.xxxxx.mongodb.net/?retryWrites=true&w=majority",
"NAME": "production_db",
},
}
3. Advanced Connection Options
For more control, use the full connection string URI format:
import os
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": os.environ.get("MONGODB_URI", "mongodb://localhost:27017"),
"NAME": os.environ.get("MONGODB_NAME", "development_db"),
"OPTIONS": {
# PyMongo connection options
"maxPoolSize": 50,
"minPoolSize": 10,
"maxIdleTimeMS": 30000,
"connectTimeoutMS": 5000,
"serverSelectionTimeoutMS": 5000,
"retryWrites": True,
"w": "majority",
},
},
}
4. Environment Variables (Recommended)
Keep sensitive data out of your code:
settings.py
import os
from pathlib import Path
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": os.environ.get("MONGODB_HOST"),
"NAME": os.environ.get("MONGODB_NAME"),
},
}
And in your .env file (never commit this!):
MONGODB_HOST=mongodb+srv://user:pass@cluster.mongodb.net
MONGODB_NAME=myapp_production
Working with Models
1. Defining Models
Django models work as expected with the MongoDB backend:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
bio = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=300)
slug = models.SlugField(unique=True)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='articles')
published = models.BooleanField(default=False)
published_at = models.DateTimeField(null=True, blank=True)
tags = models.JSONField(default=list) # Store arrays naturally!
metadata = models.JSONField(default=dict) # Store nested objects!
views = models.PositiveIntegerField(default=0)
class Meta:
ordering = ['-published_at']
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['published', '-published_at']),
]
def __str__(self):
return self.title
2. Leveraging MongoDB's Document Model
One of MongoDB's strengths is storing nested data. Use JSONField to store complex structures:
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
# Store specifications as nested document
specifications = models.JSONField(default=dict)
# Example: {"weight": "1.5kg", "dimensions": {"width": 10, "height": 20}}
# Store variants as array of documents
variants = models.JSONField(default=list)
# Example: [{"color": "red", "stock": 50}, {"color": "blue", "stock": 30}]
# Store category hierarchy
categories = models.JSONField(default=list)
# Example: ["Electronics", "Computers", "Laptops"]
3. Model Relationships
Standard Django relationships work with the MongoDB backend:
# One-to-Many
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
author_name = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
# Many-to-Many
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name='posts')
Querying Data
1. Standard Django QuerySet API
The familiar Django ORM syntax works out of the box:
# Get all published articles
published_articles = Article.objects.filter(published=True)
# Get articles by a specific author
author_articles = Article.objects.filter(author__name='John Doe')
# Complex queries
from django.db.models import Q
results = Article.objects.filter(
Q(title__icontains='python') | Q(tags__contains=['programming']),
published=True
).order_by('-published_at')[:10]
2. Field Lookups
Standard Django lookups are converted to MongoDB aggregation operations:
# Exact match
Article.objects.filter(slug='my-article')
# Case-insensitive contains
Article.objects.filter(title__icontains='django')
# Greater than / Less than
Article.objects.filter(views__gt=1000)
Article.objects.filter(published_at__lt=datetime.now())
# In list
Article.objects.filter(author_id__in=[1, 2, 3])
# Range
from datetime import datetime, timedelta
last_week = datetime.now() - timedelta(days=7)
Article.objects.filter(published_at__range=[last_week, datetime.now()])
3. Aggregations
Django's aggregation framework is fully supported:
from django.db.models import Count, Avg, Sum, Max, Min
# Count articles per author
Author.objects.annotate(article_count=Count('articles'))
# Average views per article
from django.db.models import Avg
average_views = Article.objects.aggregate(Avg('views'))
# Multiple aggregations
stats = Article.objects.aggregate(
total_views=Sum('views'),
avg_views=Avg('views'),
max_views=Max('views'),
article_count=Count('id')
)`
4. Raw Aggregation Pipelines
For advanced queries, you can use MongoDB's aggregation pipeline directly:
from django_mongodb_backend.expressions import RawAggregation
# Access the raw collection for complex aggregations
from django.db import connection
collection = connection.get_collection('myapp_article')
pipeline = [
{'$match': {'published': True}},
{'$group': {
'_id': '$author_id',
'total_views': {'$sum': '$views'},
'article_count': {'$sum': 1}
}},
{'$sort': {'total_views': -1}},
{'$limit': 10}
]
results = list(collection.aggregate(pipeline))
Migrations and Admin
1. Running Migrations
Django migrations work with MongoDB:
# Create migrations
python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Check migration status
python manage.py showmigrations
2. Django admin
The admin interface is fully functional:
# admin.py
from django.contrib import admin
from .models import Author, Article
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ['name', 'email', 'created_at']
search_fields = ['name', 'email']
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'published', 'views', 'published_at']
list_filter = ['published', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
3. Creating a Superuser
python manage.py createsuperuser
Dockerizing Django-MongoDB Application
Now, for the exciting part: containerizing your application for consistent development and deployment environments.
Project structure
myproject/
├── docker/
│ ├── Dockerfile
│ └── docker-compose.yml
├── myproject/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── myapp/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ └── admin.py
├── requirements.txt
├── manage.py
└── .env.example
1. Docker File
# Dockerfile
FROM python:3.12-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY . .
# Create non-root user for security
RUN adduser --disabled-password --gecos '' appuser && \
chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Run the application
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "myproject.wsgi:application"]
2. Docker Compose Configuration
# docker-compose.yml
version: '3.8'
services:
# MongoDB Service
mongodb:
image: mongodb/mongodb-atlas-local:latest
container_name: mongodb
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
environment:
- MONGODB_INITDB_ROOT_USERNAME=admin
- MONGODB_INITDB_ROOT_PASSWORD=password123
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh mongodb://localhost:27017 --quiet
interval: 10s
timeout: 10s
retries: 5
start_period: 40s
networks:
- app-network
# Django Application
web:
build:
context: .
dockerfile: Dockerfile
container_name: django_app
ports:
- "8000:8000"
volumes:
- .:/app
- static_volume:/app/staticfiles
environment:
- DEBUG=1
- SECRET_KEY=your-secret-key-here
- MONGODB_HOST=mongodb://admin:password123@mongodb:27017
- MONGODB_NAME=django_db
- ALLOWED_HOSTS=localhost,127.0.0.1
depends_on:
mongodb:
condition: service_healthy
command: >
sh -c "python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
networks:
- app-network
# Nginx (for production)
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
volumes:
- static_volume:/app/staticfiles
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- web
networks:
- app-network
profiles:
- production
volumes:
mongodb_data:
static_volume:
networks:
app-network:
driver: bridge
3. Requirements File
# requirements.txt
Django>=5.2,<5.3
django-mongodb-backend==5.2.*
gunicorn==21.2.0
python-dotenv==1.0.0
whitenoise==6.6.0
4. Environment configuration
# settings.py - Docker-aware configuration
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY', 'development-secret-key')
DEBUG = os.environ.get('DEBUG', '0') == '1'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',')
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # For static files in production
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
# MongoDB Database Configuration
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": os.environ.get("MONGODB_HOST", "mongodb://localhost:27017"),
"NAME": os.environ.get("MONGODB_NAME", "django_db"),
},
}
# Default primary key field type
DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField"
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
5. Running the Application
# Build and start all services
docker-compose up --build
# Run in detached mode
docker-compose up -d --build
# View logs
docker-compose logs -f web
# Run migrations
docker-compose exec web python manage.py migrate
# Create superuser
docker-compose exec web python manage.py createsuperuser
# Stop all services
docker-compose down
# Stop and remove volumes (warning: deletes data)
docker-compose down -v
6. Development vs production Docker Compose
Create a separate file for production:
# docker-compose.prod.yml
version: '3.8'
services:
mongodb:
restart: always
environment:
- MONGODB_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGODB_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
web:
restart: always
environment:
- DEBUG=0
- SECRET_KEY=${SECRET_KEY}
- MONGODB_HOST=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongodb:27017
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
command: gunicorn --bind 0.0.0.0:8000 --workers 4 myproject.wsgi:application
nginx:
restart: always
profiles: [] # Enable nginx in production
Run production:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
7. Windows-Specific Considerations
If you're developing on Windows with Docker Desktop, note that MongoDB's memory-mapped files don't work well with VirtualBox shared folders. Use Docker volumes instead of bind mounts for MongoDB data:
# Don't do this on Windows:
volumes:
- ./mongo-data:/data/db # Problematic on Windows
# Do this instead:
volumes:
- mongodb_data:/data/db # Named volume works fine
Production Considerations
1. Security Best Practices
- Use MongoDB Atlas in production: Built-in security, backups, and scaling
- Enable authentication: Always use username/password or certificate auth
- Network isolation: Use private networks and VPCs
- Encrypt at rest and in transit: TLS for connections, encryption for storage
# Production settings
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"HOST": os.environ["MONGODB_URI"], # Use Atlas connection string
"NAME": os.environ["MONGODB_NAME"],
"OPTIONS": {
"tls": True,
"tlsCAFile": "/path/to/ca-certificate.pem",
"retryWrites": True,
"w": "majority",
},
},
}
2. Performance Optimization
- Create proper indexes: Define indexes in your models
- Use connection pooling: Configure pool size based on load
- Enable query profiling: Monitor slow queries
# Model with optimized indexes
class Article(models.Model):
title = models.CharField(max_length=300, db_index=True)
slug = models.SlugField(unique=True)
published = models.BooleanField(default=False, db_index=True)
class Meta:
indexes = [
models.Index(fields=['published', '-created_at']),
models.Index(fields=['author', '-created_at']),
]
3. Monitoring
Use MongoDB Atlas monitoring or set up your own with:
- MongoDB Compass for visual exploration.
- Application Performance Monitoring (APM).
- Log aggregation for Django application logs.
Common Pitfalls and Solutions
1. Version Mismatch
Problem: ImportError or compatibility errors
Solution: Always match django-mongodb-backend version with your Django version:
Check Django version
python -c "import django; print(django.__version__)"
Install matching backend
pip install django-mongodb-backend==5.2.* # For Django 5.2.x
2. ObjectId vs Integer Primary Keys
Problem: Django expects integer primary keys by default
Solution: Use ObjectIdAutoField:
settings.py
DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField"
3. JOIN-Heavy Queries
Problem: Slow performance with many relationships
Solution: MongoDB uses $lookup for JOINs, which can be slow. Consider:
- Embedding related data in JSONField.
- Denormalizing frequently accessed data.
- Using select_related() and prefetch_related().
4. Transaction Support
Problem: Transactions behave differently
Solution: MongoDB supports multi-document transactions, but they have overhead. Design your data model to minimize transaction needs.
5. Text Search
Problem: Full-text search not working as expected
Solution: Create text indexes in MongoDB:
After migration, create text index
from django.db import connection
collection = connection.get_collection('myapp_article')
collection.create_index([('title', 'text'), ('content', 'text')])
What's Coming Next
The General Availability release planned for later in 2025 will include:
- BSON data type support: Native handling of MongoDB's data types.
- Embedded document support: First-class support for nested documents in arrays.
- Enhanced aggregation: More aggregation pipeline operators.
- Performance improvements: Optimized query translation.
The official Django MongoDB Backend represents a significant milestone for Python developers. It brings together the best of both worlds:
- Django's rapid development philosophy and rich ecosystem
- MongoDB's flexible, scalable document database
With Docker integration, you can now:
- Develop consistently across team members.
- Deploy with confidence to any environment.
- Scale your application as your needs grow.
- The combination of Django's "batteries included" approach with MongoDB's document model creates a powerful stack for modern web applications-whether you're building a simple blog or a complex enterprise system.
To Start:
Create a new project
pip install django-mongodb-backend==5.2.*
django-admin startproject myproject \
--template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.2.x.zip
Or add to an existing project
pip install django-mongodb-backend==5.2.*
Update settings.py with MongoDB configuration