Django 3.1: Async Views and JSONField for All

python backend django async

Django 3.1 landed in August 2020. Two headline features: async views finally work, and JSONField is available on all databases.

Async Views

The feature we’ve been waiting for since Django 3.0.

Writing an Async View

import asyncio
from django.http import JsonResponse

async def async_view(request):
    # Non-blocking operations
    await asyncio.sleep(1)
    return JsonResponse({'status': 'ok'})

That’s it. async def instead of def.

Async with External APIs

import httpx
from django.http import JsonResponse

async def fetch_data(request):
    async with httpx.AsyncClient() as client:
        # Concurrent requests
        responses = await asyncio.gather(
            client.get('https://api1.example.com/data'),
            client.get('https://api2.example.com/data'),
        )
    
    data = [r.json() for r in responses]
    return JsonResponse({'results': data})

What’s Still Sync

The ORM is still synchronous. Use sync_to_async:

from asgiref.sync import sync_to_async
from django.http import JsonResponse

@sync_to_async
def get_users():
    return list(User.objects.all())

async def user_list(request):
    users = await get_users()
    return JsonResponse({'users': [u.username for u in users]})

Running Async Django

# Uvicorn
uvicorn myproject.asgi:application

# Daphne
daphne myproject.asgi:application

# Gunicorn + Uvicorn workers
gunicorn myproject.asgi:application -k uvicorn.workers.UvicornWorker

When to Use Async Views

✅ External HTTP calls ✅ WebSocket connections ✅ Long-polling ✅ Streaming responses

❌ Database-heavy views (ORM still sync) ❌ CPU-intensive work (use Celery)

JSONField for All Databases

Previously PostgreSQL-only. Now works everywhere.

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    metadata = models.JSONField(default=dict)
    settings = models.JSONField(null=True, blank=True)

Database Support

DatabaseJSONField Support
PostgreSQLNative JSONB
MySQL 5.7+Native JSON
SQLite 3.9+TEXT with JSON functions
OracleCLOB with JSON functions

Querying

# Works on all databases
Product.objects.filter(metadata__key="value")
Product.objects.filter(settings__theme="dark")

# PostgreSQL-only
Product.objects.filter(metadata__has_key="dimensions")
Product.objects.filter(metadata__contains={"active": True})

Migration from postgres.fields

# Before
from django.contrib.postgres.fields import JSONField

# After
from django.db import models
# Use models.JSONField instead

Other Notable Changes

Async Middleware

class AsyncMiddleware:
    async_capable = True
    sync_capable = True
    
    def __init__(self, get_response):
        self.get_response = get_response
    
    async def __call__(self, request):
        # Async processing
        response = await self.get_response(request)
        return response

Cross-Database JSONField Indexes

from django.db import models
from django.contrib.postgres.indexes import GinIndex

class Product(models.Model):
    metadata = models.JSONField(default=dict)
    
    class Meta:
        indexes = [
            # PostgreSQL
            GinIndex(fields=['metadata']),
        ]

PathInfo Improvements

from django.urls import path

urlpatterns = [
    path('users/<int:user_id>/', views.user_detail),
]

# In view
def user_detail(request, user_id):
    # user_id is now typed as int, not str

Admin Improvements

Upgrade Path

From Django 3.0:

  1. Update requirements: django>=3.1,<3.2
  2. Replace django.contrib.postgres.fields.JSONField with django.db.models.JSONField
  3. Test async compatibility if using async views
  4. Run migrations

Breaking changes minimal for 3.0 → 3.1.

Performance Considerations

Async Overhead

Async has overhead. For simple views:

# This might be slower due to async overhead
async def simple_view(request):
    return JsonResponse({'ok': True})

# Sync is fine for no-I/O views
def simple_view(request):
    return JsonResponse({'ok': True})

Use async when you have actual async work.

JSONField Indexing

# For PostgreSQL - significant query speedup
class Meta:
    indexes = [
        GinIndex(fields=['metadata']),
    ]

# For others - consider generated columns or separate tables
# for frequently queried JSON keys

The Async Roadmap

Django 3.1 is a step, not the destination:

Full async Django is a multi-year project.

Final Thoughts

Django 3.1 delivers on the async promise begun in 3.0. Async views work. JSONField is universal.

For most projects, the upgrade is smooth. Start using async views for external API calls. Enjoy JSONField without PostgreSQL requirement.

The async future is arriving, one release at a time.


Django keeps evolving. Your stack should too.

All posts