Skip to content

Chat — Consumers

Overview

The chat.consumers module provides the realtime websocket layer for the chat app.

It is responsible for:

  • accepting websocket connections for conversation channels
  • authenticating users from the websocket scope
  • validating participant access
  • receiving realtime chat events
  • persisting chat messages
  • broadcasting message and typing events to all connected clients in a conversation

This module is the bridge between:

  • websocket clients
  • the channel layer
  • the chat service layer

Main consumer

ChatConsumer

ChatConsumer is an AsyncWebsocketConsumer that handles realtime chat traffic for a single conversation.

A connection is scoped to:

  • one authenticated user
  • one conversation
  • one channel-layer group

Conversation group

Each conversation uses a group name shaped like:

  • chat_<conversation_id>

All clients connected to the same conversation join the same group.

This allows:

  • new messages to be broadcast to all participants
  • typing events to be broadcast in real time

Responsibilities

The consumer is responsible for:

  • websocket connect and disconnect lifecycle
  • validating that the connecting user is an active participant
  • receiving JSON payloads
  • routing incoming payloads by event type
  • calling shared service functions for message creation
  • broadcasting normalized events to the conversation group

The consumer should not own core chat business rules.

Those belong in the service layer.


Connection flow

On connect

When a client opens a websocket connection:

  1. the consumer reads conversation_id from the route
  2. it reads user from the websocket scope
  3. it rejects anonymous users
  4. it checks whether the user is an active participant in the conversation
  5. if allowed:
  6. it joins the channel-layer group
  7. it accepts the websocket

Rejection codes

The consumer uses close codes such as:

  • 4401
  • unauthorized
  • 4403
  • forbidden

These help frontend clients distinguish auth failure from access failure.


Connect flow diagram

flowchart TD
    A[Websocket connect] --> B[Read conversation_id]
    B --> C[Read scope.user]
    C --> D{Authenticated?}
    D -->|no| E[Close 4401]
    D -->|yes| F{Active participant?}
    F -->|no| G[Close 4403]
    F -->|yes| H[Join chat_<conversation_id> group]
    H --> I[Accept connection]

Disconnect flow

When the socket closes, the consumer removes the channel from the conversation group.

This prevents stale group membership after browser closes, reconnects, or mobile app backgrounding.

Main behavior

  • if the connection reached group join, remove it from the group
  • otherwise do nothing

Receive flow

The consumer expects JSON payloads.

Incoming payloads are parsed and routed based on the type field.

Supported incoming types

  • message
  • typing

Unknown message types are ignored.

Invalid JSON payloads are ignored.


Receive flow diagram

flowchart TD
    A[Receive websocket payload] --> B[Parse JSON]
    B -->|invalid JSON| C[Ignore]
    B --> D[Read payload.type]
    D -->|typing| E[Handle typing]
    D -->|message| F[Handle message]
    D -->|unknown| G[Ignore]

Typing events

Purpose

Typing events are ephemeral realtime signals.

They are not persisted in the database.

They are only broadcast to currently connected clients.


Expected incoming payload

A client sends something shaped like:

  • type = typing
  • is_typing = true or false

Consumer behavior

The consumer:

  1. reads the current user from scope
  2. builds a typing event payload
  3. broadcasts it to the conversation group

Outgoing typing payload

The consumer sends a normalized payload shaped like:

  • type
  • conversation_id
  • user_id
  • is_typing

Echo suppression

Typing events are not echoed back to the sender.

This is useful because the sender already knows they are typing.


Typing flo diagram

flowchart TD
    A[Client sends typing event] --> B[Build typing payload]
    B --> C[Group send]
    C --> D[Other clients receive typing event]
    D --> E[Sender echo suppressed]

Message events

Purpose

Message events are persistent realtime chat actions.

Unlike typing, they are saved to the database.


Expected incoming payload

A client sends a payload shaped like:

  • type = message
  • body
  • data

The data field may contain:

  • attachments

Attachment handling

The consumer supports upload-first attachments.

The expected flow is:

  1. client uploads attachment via REST
  2. backend returns attachment IDs
  3. client sends websocket message with attachment IDs
  4. consumer creates the message
  5. service links any eligible unclaimed attachments to the new message

Consumer behavior

For incoming message events, the consumer:

  1. reads body
  2. reads data
  3. extracts attachment_ids
  4. calls the shared message service
  5. receives the created message and linked attachment IDs
  6. broadcasts the resulting message event to the group

Message flow diagram

flowchart TD
    A[Client sends message event] --> B[Extract body and data]
    B --> C[Extract attachment ids]
    C --> D[Call create_message service]
    D --> E[Persist message]
    E --> F[Attach eligible uploads]
    F --> G[Build outgoing message event]
    G --> H[Broadcast to conversation group]

Outgoing message payload

The consumer currently broadcasts a compact message payload containing fields such as:

  • id
  • conversation_id
  • sender_id
  • body
  • data
  • attachments
  • created_at

This shape is intentionally simple and compatible with existing tests.


For stronger frontend alignment, the websocket message payload should eventually match the main REST MessageSerializer shape as closely as possible.

That would reduce frontend-specific branching between:

  • initial REST history load
  • incoming websocket messages

Service integration

A key design goal is that the consumer should reuse the service layer instead of writing message persistence logic itself.

Current shared responsibilities with services

The consumer uses services for:

  • participant existence checks
  • message creation
  • attachment linking

This keeps websocket behavior aligned with REST behavior.

Why this matters

Without shared services, websocket paths and REST paths often drift apart over time.

That causes:

  • inconsistent validation
  • inconsistent payloads
  • duplicated bugs
  • harder frontend integration

Consumer helper functions

The consumer may use small async wrappers such as:

  • participant access checks
  • message creation calls

These wrappers exist because Django ORM and service calls must be bridged safely into async consumer code.

They should remain thin.

They are transport helpers, not business logic.


Relationship to websocket auth

The consumer depends on websocket authentication middleware to populate:

  • scope["user"]

The consumer does not validate JWT tokens directly.

That belongs to the websocket auth middleware.

This separation keeps the consumer focused on chat behavior rather than authentication mechanics.


Relationship to routing

The consumer is mounted in websocket routing under a route pattern such as:

  • /ws/chat//

Routing is responsible for:

  • extracting conversation_id
  • attaching middleware
  • resolving the consumer class

The consumer is responsible for everything after routing hands control over.


Responsibilities by layer

Consumer layer owns

  • realtime connection lifecycle
  • websocket payload parsing
  • group membership
  • event broadcasting
  • typing event handling

Service layer owns

  • participant access validation
  • message persistence
  • attachment linking
  • notification hooks

Auth middleware owns

  • turning token or query credentials into scope.user

Models layer owns

  • persistence and relationships

What consumers do not do

Consumers do not:

  • define REST endpoints
  • return HTTP responses
  • validate upload policy directly
  • compute unread counts
  • manage org scoping headers
  • define serializer fields
  • own long-term business rules for chat workflows

Those responsibilities belong to:

  • views
  • services
  • serializers
  • middleware

Realtime event types

Incoming client-to-server events

  • message
  • typing

Outgoing server-to-client events

Currently:

  • message payloads
  • typing payloads

Possible future outgoing events

The consumer layer is well-positioned to later support:

  • message.edited
  • message.deleted
  • participant.joined
  • participant.left
  • read.updated
  • conversation.closed

Frontend integration notes

For frontend readiness, clients should treat websocket usage like this:

Websocket is best for

  • receiving new messages in real time
  • receiving typing indicators
  • future live updates

REST is best for

  • initial conversation list
  • initial history load
  • pagination of old messages
  • attachment upload
  • marking read
  • participant management
  • edit/delete actions unless realtime sync is added later

This separation is normal and keeps the frontend simpler.

Connection lifecycle summary

stateDiagram-v2
    [*] --> Connecting
    Connecting --> RejectedUnauthorized
    Connecting --> RejectedForbidden
    Connecting --> Connected
    Connected --> ReceivingEvents
    ReceivingEvents --> BroadcastingTyping
    ReceivingEvents --> BroadcastingMessage
    BroadcastingTyping --> ReceivingEvents
    BroadcastingMessage --> ReceivingEvents
    ReceivingEvents --> Disconnected
    Disconnected --> [*]

Design principles

Thin consumer, shared services

The consumer should orchestrate realtime behavior, not own chat business rules.

Participant-based access

Realtime access is controlled through active participant membership.

Ephemeral vs persistent events

Typing is ephemeral.
Messages are persistent.

Group-per-conversation model

Each conversation maps to one channel-layer group.

This is a natural fit for chat.

Frontend-safe event handling

Incoming payloads should be simple and predictable, and unknown payloads should fail safely by being ignored.


Best practices

  • keep message persistence inside services
  • keep typing events non-persistent
  • suppress typing echo to the sender
  • reject anonymous and non-participant connections early
  • keep websocket payload shapes stable
  • align websocket message shape with REST serializers over time

Future extensions

Possible future improvements include:

  • message edit/delete websocket broadcasts
  • read receipt broadcasts
  • conversation presence
  • server-side typing throttling
  • delivery acknowledgements
  • system event broadcasting
  • reconnect resume hints
  • serializer-backed websocket event payloads

Summary

The chat.consumers module provides the realtime transport layer for chat.

It handles:

  • authenticated websocket connections
  • participant-based access control
  • realtime typing events
  • realtime message creation and broadcast

Together with the service layer, it makes the chat app:

  • realtime-capable
  • cleanly structured
  • easier to evolve
  • more ready for frontend integration