Building a Realtime Chat App with Django Channels and WebSockets

honeybadger_staff

Honeybadger Staff

Posted on December 16, 2022

Building a Realtime Chat App with Django Channels and WebSockets

This article was originally written by Muhammed Ali on the Honeybadger Developer Blog.

Django is well known for being used to develop servers for HTTP connections and requests for applications. Unfortunately, when building applications that require the connection to remain open for a two-way connection, such as conferencing and chatting applications, using an HTTP connection is inefficient. This is where WebSockets come into play.

WebSockets provide a means of opening a two-way connection between the client and the server so that all users connected to the open network can get related data in real time. It is a stateful protocol, which means connection authentication is only required once; the client credential is stored, and there is no further need for authentication until the connection is lost.

In this article, I’ll briefly introduce you to WebSocket and its usefulness. Then, I’ll show you how to use it in Django using Django channels and create WebSocket connections with JavaScript to connect with the Django Server.

We will build a simple chatbox to make things more realistic. You can find the code on GitHub.

Prerequisites

  • Basic understanding of Django.
  • Basic understanding of JavaScript.

Characteristics of WebSockets

  • WebSockets is a bidirectional protocol. Therefore, the client and server can exchange data simultaneously without delays or intervention. WebSockets is considered full-duplex communication for the same reason.
  • WebSockets is a stateful protocol. Therefore, after initial connection authentication, the client credential is saved, and further authentication is not required until the connection is lost.
  • WebSockets doesn’t need any special browsers to function; it works on all browsers.

When to Use WebSockets

WebSockets is used when you want to build any kind of real-time application, ranging from complex applications, such as multiplayer games played on the internet, to less complex ones, such as chat applications.

An alternative method of building a chat application without using WebSockets is using JavaScript to query the database after a few seconds to get current data from the chatbox. As you can imagine, this is not scalable because if there are thousands of users, the number of requests it could generate might cause the server to crash. Additionally, this method will not when you want to build something like a video call application.

How to Use WebSockets in Django

Using WebSockets in Django utilizes asynchronous Python and Django channels, making the process straightforward. Using Django channels, you can create an ASGI server, and then create a group where users can send text messages to all the other users in the group in real time. This way, you are not communicating with a particular user, but with a group, multiple users can be added.

If you are chatting with your friend on Twitter, you and your friend are in one group, represented by the chatbox.

Configure Django to Use ASGI

If you don’t already have a Django project, create a folder where you want to store the code for your project, cd into it, and run startproject to create a new Django project:

django-admin startproject project .
Enter fullscreen mode Exit fullscreen mode

Now, create a new Django app by running $ python3 manage.py startapp app.

You need to inform your Django project that a new app has been added. To do this, update the project/settings.py file and add 'app' to the INSTALLED_APPS list. It’ll look like this:

# project/settings.py
INSTALLED_APPS = [
   ...
   'chat',
]
Enter fullscreen mode Exit fullscreen mode

Now install Django channels by running the following command on your command line.

pip install channels
Enter fullscreen mode Exit fullscreen mode

Update the project/settings.py file and add 'channels' to the INSTALLED_APPS list:

# project/settings.py
INSTALLED_APPS = [
   ...
   'channels',
]
Enter fullscreen mode Exit fullscreen mode

While you are in the settings.py file, you need to set a configuration to enable the Django channel and Django to communicate with each other using a message broker. We can use a tool like Redis for this, but for this tutorial, we will use the local backend. Paste the following code into your settings.py file:

ASGI_APPLICATION = "project.routing.application" #routing.py will be created later
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': "channels.layers.InMemoryChannelLayer"
        }
    }
Enter fullscreen mode Exit fullscreen mode

In the code above, ASGI_APPLICATION is needed to run the ASGI server and tell Django what to do when an event happens. This configuration will be placed in a file named routing.py.

Build a Minimalistic Chatbox

Next, we will create a chatbox that authenticated users can access via a URL and chat with each other. To get this going, open your app/views.py file and paste the code below to pass the chatbox name from the URL to the HTML file (chatbox.html):

from django.shortcuts import render

def chat_box(request, chat_box_name):
    # we will get the chatbox name from the url
    return render(request, "chatbox.html", {"chat_box_name": chat_box_name})
Enter fullscreen mode Exit fullscreen mode

Now, replace the code you have in project/urls.py with the following code. This will handle the chatbox name stated in the browser (http://127.0.0.1:8002/chat/**chatboxname**/).

from django.contrib import admin
from django.urls import path
from app.views import chat_box

urlpatterns = [
    path("admin/", admin.site.urls),
    path("chat/<str:chat_box_name>/", chat_box, name="chat"),
]
Enter fullscreen mode Exit fullscreen mode

Next, let’s start working on the consumers. Consumers in channels help structure your code as a series of functions to be called whenever an event happens. Consumers are usually written in asynchronous Python. To start, create a new file in app/ folder named consumers.py and paste the code shown below. What the following code is doing is handling what happens when the server connects, disconnects, receives a request, or sends a text.

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatRoomConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.chat_box_name = self.scope["url_route"]["kwargs"]["chat_box_name"]
        self.group_name = "chat_%s" % self.chat_box_name

        await self.channel_layer.group_add(self.group_name, self.channel_name)

        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.group_name, self.channel_name)
    # This function receive messages from WebSocket.
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        username = text_data_json["username"]

        await self.channel_layer.group_send(
            self.group_name,
            {
                "type": "chatbox_message",
                "message": message,
                "username": username,
            },
        )
    # Receive message from room group.
    async def chatbox_message(self, event):
        message = event["message"]
        username = event["username"]
        #send message and username of sender to websocket
        await self.send(
            text_data=json.dumps(
                {
                    "message": message,
                    "username": username,
                }
            )
        )

    pass
Enter fullscreen mode Exit fullscreen mode

Now, let’s add the code for routing.py, which was mentioned earlier. Create a file in your /project folder named routing.py and paste the following code:

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from app import consumers

# URLs that handle the WebSocket connection are placed here.
websocket_urlpatterns=[
                    re_path(
                        r"ws/chat/(?P<chat_box_name>\w+)/$", consumers.ChatRoomConsumer.as_asgi()
                    ),
                ]

application = ProtocolTypeRouter( 
    {
        "websocket": AuthMiddlewareStack(
            URLRouter(
               websocket_urlpatterns
            )
        ),
    }
)
Enter fullscreen mode Exit fullscreen mode

Next, let’s build the frontend for the application. Create a file in app/templates with the name chatbox.html, and then paste the code shown below. Most of this code is the stater template code from bootstrap, just to give the application some styling.

<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
        integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

</head>

<body>

    <div class="container">
        <div class="row d-flex justify-content-center">
            <div class="col-3">
                <form>
                    <div class="form-group">
                        <label for="exampleFormControlTextarea1" class="h4 pt-5">Chatbox</label>
                        <textarea class="form-control" id="chat-text" readonly rows="10"></textarea><br>
                    </div>
                    <div class="form-group">
                        <input class="form-control" placeholder="Enter text here" id="input" type="text"></br>
                    </div>
                    <input class="btn btn-primary btn-lg btn-block" id="submit" type="button" value="Send">
                </form>
            </div>
        </div>
    </div>
    {% comment %} Get data for username and chatbox name{% endcomment %}
    {{ request.user.username|json_script:"user_username" }}
    {{ chat_box_name|json_script:"room-name" }}


    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous">
    </script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous">
    </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
        integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous">
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Next, we’ll develop the JavaScript code that will fetch the data and handle the WebSocket connection from the frontend side. Paste the following code above the first <script> tag in the HTML file that you just created.

<script>
   const user_username = JSON.parse(document.getElementById('user_username').textContent);
   document.querySelector('#submit').onclick = function (e) {
      const messageInputDom = document.querySelector('#input');
      const message = messageInputDom.value;
      chatSocket.send(JSON.stringify({
          'message': message,
          'username': user_username,
      }));
      messageInputDom.value = '';
   };

   const boxName = JSON.parse(document.getElementById('box-name').textContent);
   # Create a WebSocket in JavaScript.
   const chatSocket = new WebSocket(
      'ws://' +
      window.location.host +
      '/ws/chat/' +
      boxName +
      '/'
   );

   chatSocket.onmessage = function (e) {
      const data = JSON.parse(e.data);
      document.querySelector('#chat-text').value += (data.message + ' sent by ' + data.username   + '\n') // add message to text box
   }
</script>
Enter fullscreen mode Exit fullscreen mode

Now, run the following commands to migrate the authentication model so that you can create new users to test the application:

python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

You can create new users by running the following:

python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Finally, you can test the application by running it and logging in to two users using Django admin. You will be able to do this by logging in to each of the users on different browsers. Then, open the URL 127.0.0.1:8000/chat/newbox/ on each of the browsers, and when you send a text, each user receives the text in real time.

output of the final app

Conclusion

In this article, you’ve learned about WebSocket and its usefulness, as well as how to use it in Django using Django channels. Finally, you learned how to create WebSocket connections with JavaScript to connect with the Django server.

Although we successfully built a real-time chat application, there is still more you could add to it. For example, you could add a database connection to store messages. You could also think about using Redis as the message broker instead of the local backend.

💖 💪 🙅 🚩
honeybadger_staff
Honeybadger Staff

Posted on December 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related