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:
- the consumer reads
conversation_idfrom the route - it reads
userfrom the websocket scope - it rejects anonymous users
- it checks whether the user is an active participant in the conversation
- if allowed:
- it joins the channel-layer group
- 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]
¶
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]
¶
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:
- reads the current user from scope
- builds a typing event payload
- 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]
¶
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:
- client uploads attachment via REST
- backend returns attachment IDs
- client sends websocket message with attachment IDs
- consumer creates the message
- service links any eligible unclaimed attachments to the new message
Consumer behavior¶
For incoming message events, the consumer:
- reads body
- reads data
- extracts attachment_ids
- calls the shared message service
- receives the created message and linked attachment IDs
- 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]
¶
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.
Recommended future direction¶
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