Deploying a DRF API to production
The Django from scratch series' deployment part already covers what every Django project needs in production — SECRET_KEY and DEBUG from environment variables, ALLOWED_HOSTS, WhiteNoise for static files, a real Postgres database, and gunicorn as the actual server. Every bit of that applies here unchanged. This part covers what's specific to an API on top of it.
CORS: letting a separate frontend actually call this API
pip install django-cors-headers# taskapi/settings.py
INSTALLED_APPS = [
"corsheaders",
# ... the rest ...
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
# ... the rest ...
]
CORS_ALLOWED_ORIGINS = [
"https://your-frontend.com",
]A browser blocks JavaScript on your-frontend.com from calling an API on a different domain unless that API explicitly allows it — Django's session-based CSRF protection assumes same-origin requests, but a separate frontend calling this API is a fundamentally cross-origin situation, and CORS is the mechanism that allows it deliberately instead of by accident. CorsMiddleware has to sit near the top of MIDDLEWARE, before CommonMiddleware. CORS_ALLOWED_ORIGINS is an explicit allowlist — exactly the same reasoning as ALLOWED_HOSTS, never wildcarded to "*" for an API that handles authenticated requests, since that would let literally any website's JavaScript make authenticated calls on a logged-in user's behalf.
A real cache backend for throttling
Part 17's throttling relies on Django's cache framework to track request counts — the local-memory cache used in development doesn't share state across the multiple worker processes a real gunicorn deployment runs, which means each process would enforce its own separate rate limit instead of one shared one.
pip install django-redis# taskapi/settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.environ["REDIS_URL"],
}
}Redis is the standard choice here — most hosts that offer managed Postgres offer a managed Redis instance alongside it, and django-redis is what connects Django's cache framework (and therefore DRF's throttling from part 17) to it.
Turning off the login form, keeping the API on
# taskapi/urls.py — remove or gate this in production
# path("api-auth/", include("rest_framework.urls")),The browsable API's session-based login (part 18) is genuinely useful for development and manual testing; a production API consumed entirely by a separate frontend and mobile clients often has no real use for it. Removing api-auth/ doesn't touch TokenAuthentication at all — API clients keep working exactly as before, this only removes the session-login convenience for browsing the API by hand.
After deploying, hit /api/schema/ (part 21) first — a working schema response confirms routing, settings, and the app import chain are all correct before debugging anything database- or auth-related. It's the same idea as the health-check-first checkpoint from the Node.js REST API series' own deployment part, just using something this project already has.
What you built, across both series
Between the Django from scratch series and this one, that's 44 parts covering a server-rendered web application and a JSON API built on the same underlying framework — models, migrations, and the admin shared between both, then two entirely different response layers built on top: HTML templates with forms and session auth on one side, serializers, viewsets, and token auth on the other. Real Django projects are very often both at once, and everything in these two series is the actual foundation either one is built from.