How we build Bytebase — Part Two: Extending Django to support Real-time Apps with Django Channels

This is a second post in a series adapted from the Django Real-Time Chat tutorial we presented at the 2019 US DjangoCon conference in San Diego. Here’s the code.

Part One covered why we chose WebSockets and how they work.

This post will cover:

  • the motivation for Django Channels
  • the core building blocks for Channels
  • how to build our first chat server by following the official Django Channels tutorial

If you don’t know us, Bytebase is the byte-size knowledge base for engineering teams. Bytebase supports collaborating on a module in real-time.

In this post, we share how we used Django Channels to enable our web application for Real-Time communication.

How Django Channels Works

Django Channels is a project that extends Django to work across any protocol, not just HTTP. Channels extends Django by adding an asynchronous layer to support long-running connections.

Without Channels, Django code is synchronous.

Synchronous code

Synchronous code is executed one line after the next. Any pauses in execution do not provide an opportunity for other code to run.

Asynchronous code

Asynchronous code can pause and let other code take over. It allows for concurrency, which is the ability to run in parallel.

Sync vs Async

To understand the difference between synchronous and asynchronous code, consider this example, adapted from this Introduction to Async Programming.

Suppose Alice is a chess player and needs to complete two chess games. If Alice plays synchronously, then she plays one game to completion, and then play the next one. While Alice’s opponent is deciding on her next move, Alice is waiting.

Synchronous — Alice plays the first game to completion. Then she plays the second game to completion.

If Alice plays asynchronously, she can jump between games. While Alice’s opponent is deciding her next move, Alice can make a move in a different chess game.

Asynchronous — Alice can alternate between the two chess games.

Django Channels enables asynchronous support for Django applications. This makes applications more performant when it comes to I/O and waits.

Our chat app will leverage this asynchronous capability, where a WebSocket client is always open and waiting for new messages to arrive.

Why we need Async

Asynchronous capabilities are a pre-requisite for supporting the WebSocket protocol. While one or more long-lived WebSocket connections are open, our web server needs to listen for incoming messages. We don’t want the server to block while listening, so we require our server to be asynchronous.

Core components of Django Channels

Django Channels adds an asynchronous layer to Django applications. It lets Django programmers use the same concepts they’re already familiar with, and adds the following:

Consumer

A Consumer is similar to a Django View, but it’s built for both synchronous and asynchronous code. It’s called a “consumer” because it consumes events.

Here’s a sample consumer from the official Channels Tutorial. The Consumer defines behavior for connecting to the client, disconnecting, and receiving messages from the client.

Our first consumer.

With this consumer, you can create messages and view them in your browser. You cannot yet communicate with other clients. See code for this behavior.

We want to have real-time communication with other clients too. To do so, we need to add a Channels Layer.

Channels Layer

The Channels Layer is a backend that enables clients to talk to each other.

A Channel is a mailbox where messages can be sent to.

A Group is a group of related channels. You can send messages to all Channels in a Group.

A Channel Backend is a persistent store that’s responsible for:

  • Storing the groups and the channels that belong to them.
  • Routing messages to the right groups.

We use Redis as our channel backend.

Using Django Channels for real-time communication between clients

First, we’ll update our consumer to send messages to all channels in a group. It will still send messages to itself too.

Update Consumer to send event to all channels in a group

Next, we’ll configure Redis as our backend. We use the channels_redis library to configure Redis as our backend. To do so, we add this configuration in settings.py.

Configure Redis Channels Backend in settings.py.

Now we have consumers that can talk to other clients and we have a redis backend that knows how to route messages between clients.

Check out this code to run it yourself.

Thanks for reading!

Bytebase, the byte-size knowledge base.

Bytebase is a web app that helps engineering teams share knowledge better. Everything in Bytebase is a “byte” — a short chunk of information. Bytes can be grouped together and organized, using the same principles engineers use to organize their code.

Check us out at bytebase.io or email me with subject line ‘Builder’ for access.

Written by

This is the official blog of https://bytebase.io— the fastest way to write, organize, and collaborate on notes.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store