Core Concepts
Before diving into Fast Channels, it’s important to understand the two fundamental building blocks: Consumers and Channel Layers. These concepts form the foundation of real-time communication in Fast Channels.
Consumers
A consumer is the heart of your WebSocket application - it’s where you define how your application responds to WebSocket events like connections, messages, and disconnections.
Consumer Types
Fast Channels provides three main consumer classes to choose from:
- AsyncConsumer
The base consumer class that handles any ASGI protocol. Use this for custom protocols or when you need low-level control over message handling.
- AsyncWebsocketConsumer
The most commonly used consumer for WebSocket connections. Provides convenient methods for handling WebSocket events and automatic group management.
- AsyncJsonWebsocketConsumer
Extends AsyncWebsocketConsumer with automatic JSON encoding/decoding. Perfect for applications that primarily exchange JSON data.
Basic WebSocket Consumer Structure
Most applications will use AsyncWebsocketConsumer which implements three main methods:
from fast_channels.consumer.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
# Called when a WebSocket connection is opened
await self.accept() # Accept the connection
async def receive(self, text_data=None, bytes_data=None, **kwargs):
# Called when a message is received from the client
await self.send(text_data="Echo: " + text_data)
async def disconnect(self, close_code):
# Called when the WebSocket connection is closed
pass
Key Consumer Methods
Connection Management:
accept()- Accept an incoming WebSocket connectionclose()- Close the WebSocket connectionsend(text_data, bytes_data)- Send text or binary data to the client
JSON Consumer Methods (AsyncJsonWebsocketConsumer):
send_json(content)- Automatically encode and send JSON datareceive_json(content)- Override this to handle incoming JSON messagesencode_json(content)/decode_json(text_data)- Customize JSON serialization
Groups:
Define a groups list to automatically join/leave groups on connect/disconnect:
class ChatConsumer(AsyncWebsocketConsumer):
groups = ["chat_room", "notifications"] # Auto-join these groups
Dynamic Group Management: For more flexibility, manually manage groups based on URL parameters or user data:
class RoomChatConsumer(AsyncWebsocketConsumer):
channel_layer_alias = "chat"
async def connect(self):
# Extract room name from path parameters: /ws/room/{room_name}/
self.room_name = self.scope['path_params']['room_name']
self.room_group = f'room_{self.room_name}'
# Extract user ID from query params: /ws/room/123/?user_id=456
query_string = self.scope['query_string'].decode()
# Parse query_string manually or use urllib.parse.parse_qs
await self.accept()
# Join room-specific group
await self.channel_layer.group_add(self.room_group, self.channel_name)
async def disconnect(self, close_code):
# Leave the group
await self.channel_layer.group_discard(self.room_group, self.channel_name)
The Scope - Your Request Context
Every consumer has access to self.scope, which contains connection information similar to a Django request object:
scope["path"]- The WebSocket URL pathscope["query_string"]- URL query parameters as bytesscope["headers"]- HTTP headers from the connectionscope["cookies"]- Cookies sent by the clientscope["user"]- User object (if authentication middleware is used)
This information is useful for authentication, routing users to specific groups, or customizing behavior based on the connection:
async def connect(self):
# Get user from scope
user = self.scope.get("user")
if user and user.is_authenticated:
# Join user-specific group
room_id = self.scope["path"].split("/")[-1]
await self.accept()
else:
await self.close()
Why Async Only?
Unlike Django Channels, Fast Channels focuses exclusively on async consumers. This decision reflects the async-native nature of modern ASGI frameworks like FastAPI and Starlette. Since these frameworks are built around async/await patterns, using async consumers provides:
Better performance and resource utilization
Seamless integration with existing async codebases
Elimination of complex sync-to-async conversions
Channel Layers
Without channel layers, your WebSocket consumer can only communicate directly with its connected client - like a simple client-server connection. Channel layers unlock the real power of real-time applications by enabling cross-process communication.
Setting Up Channel Layers
Before using channel layers, you need to register them first and then reference them in your consumers:
# Register a channel layer
from fast_channels.layers import register_channel_layer, RedisChannelLayer
register_channel_layer("chat", RedisChannelLayer(hosts=["redis://localhost:6379"]))
# Use it in your consumer
class ChatConsumer(AsyncWebsocketConsumer):
channel_layer_alias = "chat" # Reference the registered layer
groups = ["chat_room"]
What Channel Layers Enable
Group Messaging: Send messages to multiple WebSocket connections simultaneously. Perfect for chat rooms, live updates, or broadcasting notifications.
Background Worker Integration: Send messages from HTTP endpoints, background tasks, or separate processes to WebSocket clients. This enables patterns like:
HTTP API triggering real-time notifications
Background jobs sending progress updates
System events broadcasting to connected users
Multi-Instance Applications: Scale your application across multiple processes or servers while maintaining real-time communication between all parts.
Core Channel Layer Operations
Send to Specific Connection:
# Send a message to a specific channel (connection)
await channel_layer.send(channel_name, {
"type": "notification.message",
"content": "Your order is ready!"
})
Send to Groups:
# Broadcast to all connections in a group
await channel_layer.group_send("chat_room_1", {
"type": "chat.message",
"username": "Alice",
"message": "Hello everyone!"
})
Group Management:
# Add a connection to a group
await channel_layer.group_add("notifications", self.channel_name)
# Remove a connection from a group
await channel_layer.group_discard("notifications", self.channel_name)
How Messages Flow
When you send a message through the channel layer, it gets routed to the appropriate consumer method based on the
message type. The type uses dot notation, which gets converted to method names:
"chat.message"→ callschat_message()method"user.notification"→ callsuser_notification()method"system.alert"→ callssystem_alert()method
class ChatConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data=None, **kwargs):
# User sends a message, broadcast it to the group
await self.channel_layer.group_send("chat_room", {
"type": "chat.message",
"message": text_data,
"username": "user123"
})
async def chat_message(self, event):
# Handle the broadcast message and send to WebSocket
await self.send_json({
"message": event["message"],
"username": event["username"]
})
Channel Layer Backends
Fast Channels supports multiple channel layer backends:
In-Memory (Testing Only): - Single process only - Perfect for unit tests and development - Cannot communicate across processes
Redis Queue Layer (Production): - Reliable message delivery with persistence - Supports multiple processes and servers - Best for critical messaging where delivery matters
Redis Pub/Sub Layer (Real-time): - Ultra-low latency for live communication - No message persistence - Perfect for chat and live updates
The choice of backend depends on your specific needs - use in-memory for testing, Redis Queue for reliability, or Redis Pub/Sub for speed.
Putting It Together
Consumers and channel layers work together to create powerful real-time applications:
Consumers handle WebSocket connections and define your application logic
Channel layers enable communication between different parts of your system
Groups allow you to organize and broadcast to multiple connections
Message routing automatically dispatches channel layer messages to the right consumer methods
This architecture enables you to build anything from simple chat applications to complex distributed systems with real-time updates, all while maintaining clean, readable code that leverages the proven patterns from Django Channels.
Ready to see this in action? Check out our Tutorial for a hands-on walkthrough of building a real-time application with Fast Channels.