← Back to portfolio
Case Study · 13 / 17
Real-Time · Full-Stack · 2025

Messages that arrive
the moment they're sent.

DjangoChatify is a real-time chat app built on WebSockets via Django Channels, backed by Redis and Postgres, containerised end to end, and wrapped in a CI/CD pipeline. The theme: making Django — a request/response framework — hold a persistent connection open, cleanly.

Role
Solo — full-stack & infra
Discipline
Real-time systems
Stack
Django Channels · React · Redis · Postgres · Docker
Status
Open-source, Dockerised
Fig. 13 · socket open, always
13
Polling asks "anything new?" a thousand times. A socket just tells you.
§ 01 — The problem

Chat over HTTP polling is a bad impression of real-time.

You can fake a chat app by polling an endpoint every second, but it's wasteful, laggy, and doesn't scale — a thousand clients asking "anything new?" on a loop is a thousand pointless requests. Real-time messaging wants a persistent connection that pushes, not a client that keeps knocking.

DjangoChatify is the exercise of doing it properly: WebSockets through Django Channels, with Redis as the channel layer so messages fan out to the right clients instantly.

Real-time isn't faster polling. It's not polling at all.

§ 02 — Constraints

What "production-ready" meant here.

§ 03 — Architecture

Redis is the switchboard.

The React client holds a WebSocket to Django Channels; Redis is the channel layer that routes a message to everyone in a room; Postgres persists the history; Gunicorn and Docker package it for deployment.

01 — Client
React socket
Tailwind UI opens a WebSocket and renders messages as they arrive.
02 — Consumer
Django Channels
Async consumers accept connections and handle inbound messages.
03 — Fan-out
Redis layer
The channel layer broadcasts each message to every client in the room.
04 — Persist
Postgres
Messages stored with optimised models for history and replay.

Redis is the piece that makes it scale beyond a single process: without a shared channel layer, two Django workers can't see each other's clients, so a message sent to worker A never reaches a user on worker B.

§ 04 — What broke first

Sync Django doesn't hold a connection open.

The friction was conceptual before it was technical: Django's model is request-in, response-out, and a WebSocket is the opposite — a connection that stays open and pushes both ways. Making that work meant the async consumer stack (ASGI, not WSGI) and getting the Redis channel layer configured so broadcasts actually reached other workers.

Once the connection lifecycle and the channel layer were right, everything else — rooms, presence, history — was ordinary Django. The hard part was the paradigm shift, and the tests exist to make sure it stays shifted.

1
Persistent socket per client — no polling
2
Health signals watched — database and Redis
CI
GitHub Actions gate with unit + WebSocket tests
§ 05 — Operability

Built to actually run.

The infra choices are the point as much as the chat: a non-root container, a health-check endpoint, environment-driven config for DEBUG and ALLOWED_HOSTS, and a CI pipeline that won't let broken code merge. It's a small app deliberately wearing production clothes, because that's the part worth practising.

§ 06 — What I'd do differently

Three things, next time.