Building a Chat Application with Python, Django and React - Tutorial

Building a Chat Application with Python, Django and React - Tutorial

This tutorial will explain how to build a chat application with Python, Django and React in just 7 steps .Unlike other tutorials, I’m not using Python/Django for WebSocket connections. While doing so may seem cool from a tech perspective, it's pretty sluggish and expensive – especially if you have a halfway decent number of users. Languages such as C++, Go and Elixir are much better at handling the core of chat.

Unlike other tutorials, I’m not using Python/Django for WebSocket connections. While doing so may seem cool from a tech perspective, it's pretty sluggish and expensive – especially if you have a halfway decent number of users. Languages such as C++, Go and Elixir are much better at handling the core of chat.

In this tutorial, we will use Stream, an API for chat that takes care of WebSocket connections and other heavy lifting using Go, Raft and RocksDB.

Step 1. React Chat Demo UI

Before we start thinking about the Python chat side of things let's spin up a simple React frontend, so we have something nice and visual to look at:

$ yarn global add create-react-app
$ brew install node && brew install yarn # skip if installed
$ create-react-app chat-frontend
$ cd chat-frontend
$ yarn add stream-chat-react

Replace the code in src/App.js with:

import React from "react";
import {
  Chat,
  Channel,
  ChannelHeader,
  Thread,
  Window
} from "stream-chat-react";
import { MessageList, MessageInput } from "stream-chat-react";
import { StreamChat } from "stream-chat";

import "stream-chat-react/dist/css/index.css";

const chatClient = new StreamChat("qk4nn7rpcn75"); // Demo Stream Key
const userToken =
  "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1za3ktOSJ9.mhikC6HPqPKoCP4aHHfuH9dFgPQ2Fth5QoRAfolJjC4"; // Demo Stream Token

chatClient.setUser(
  {
    id: "cool-sky-9",
    name: "Cool sky",
    image: "https://getstream.io/random_svg/?id=cool-sky-9&name=Cool+sky"
  },
  userToken
);

const channel = chatClient.channel("messaging", "godevs", {
  // image and name are required, however, you can add custom fields
  image:
    "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png",
  name: "Talk about Go"
});

const App = () => (
  <Chat client={chatClient} theme={"messaging light"}>
    <Channel channel={channel}>
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageInput />
      </Window>
      <Thread />
    </Channel>
  </Chat>
);

export default App;

Next, run yarn start to see the chat in action!

Step 2. Django/Python Setup

Make sure you have Python 3.7 up and running.

$ brew install python3

$ pip install virtualenv virtualenvwrapper
$ export WORKON_HOME=~/Envs
$ source /usr/local/bin/virtualenvwrapper.sh
$ mkvirtualenv chatexample -p `which python3`
$ workon chatexample

If that does not work, please try this snippet:

$ python3 -m venv chatexample
$ source chatexample/bin/activate

Now that you’re in your virtual env you should see python 3 when you run:

$ python --version

To kick off a new Django project, use the following snippet:

$ pip install django
$ django-admin startproject mychat

And to start your app:

$ cd mychat
$ python manage.py runserver

Now, when you open http://localhost:8000, you should see this:

Django

Step 3. User Auth

As a next step lets setup Django’s user auth.

$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

Visit http://localhost:8000/admin/ and login. Voila!

You should see the Django admin screen as shown below:

Django Admin

Step 4. Django Rest Framework

One of my favorite packages for integrating react with Django is Django Rest Framework. To make everything work, we will need to create endpoints for:

  • User Signup
  • User Login

We could build those ourselves; however, there is a package called Djoser that has already solved this problem. It configured the necessary API endpoints for user registration, login, password reset, etc.

To install Djoser, use the following snippet:

$ pip install djangorestframework djoser

Then, edit urls.py and change the file to contain:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.authtoken')),
]

Once complete, edit settings.py and make the following changes:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'djoser',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    )
}

For more on the API endpoints that Djoser exposes.

Now, let’s go ahead and test the registration endpoint:

$ curl -X POST http://127.0.0.1:8000/auth/users/ --data 'username=djoser&password=alpine12'

Step 5. Generating Tokens to Access Stream's Chat Server

Now we need to customize the Djoser views to generate tokens for Stream. Let’s get started.

Let's organize our files a bit and create a chat app folder in our project (make sure that you are in the correct directory):

$ python manage.py startapp auth

Install stream-chat:

$ pip install stream-chat

Create a custom serializer in auth/serializers.py with the following logic:

from djoser.serializers import TokenSerializer
from rest_framework import serializers
from djoser.conf import settings as djoser_settings
from stream_chat import StreamChat
from django.conf import settings

class StreamTokenSerializer(TokenSerializer):
    stream_token = serializers.SerializerMethodField()

    class Meta:
        model = djoser_settings.TOKEN_MODEL
        fields = ('auth_token','stream_token')

    def get_stream_token(self, obj):
        client = StreamChat(api_key=settings.STREAM_API_KEY, api_secret=settings.STREAM_API_SECRET)
        token = client.create_token(obj.user.id)

        return token

And last, use the custom serializer by updating your settings.py file:

STREAM_API_KEY = YOUR_STREAM_API_KEY # https://getstream.io/dashboard/
STREAM_API_SECRET = YOUR_STREAM_API_SECRET
DJOSER = {
    'SERIALIZERS': {
        'token': 'auth.serializers.StreamTokenSerializer',
    }
}

Rerun your migration:

$ python manage.py migrate

To verify that it works, hit the login endpoint with a POST request:

$ curl -X POST http://127.0.0.1:8000/auth/token/login/ --data 'username=djoser&password=alpine12'

Both the auth_token and stream_token should be returned.

Step 6. Integrating Auth in React

Adding an auth later to the frontend is an essential step for obvious reasons. In our case, it’s especially useful because we can fetch a user token from the backend API (powered by Python) and dynamically use it when sending messages.

First, install the CORS middleware package for Django:

$ pip install django-cors-headers

Then, modify your settings.py to reference the djors-cors-header middleware:

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

And finally, add the following to your settings.py file:

CORS_ORIGIN_ALLOW_ALL = True

The next step requires a few modifications to be made to your frontend. To start, you will want to ensure that you have all of the dependencies installed via yarn:

$ yarn add axios react-dom react-router-dom

Next, create the following files within your src/ directory:

  • AuthedRoute.js
  • UnauthedRoute.js
  • withSession.js
  • Login.js
  • Chat.js

App.js

import React from "react";
import { BrowserRouter as Router, Switch } from "react-router-dom";

import Chat from "./Chat";
import Login from "./Login";

import UnauthedRoute from "./UnauthedRoute";
import AuthedRoute from "./AuthedRoute";

const App = () => (
  <Router>
    <Switch>
      <UnauthedRoute path="/auth/login" component={Login} />
      <AuthedRoute path="/" component={Chat} />
    </Switch>
  </Router>
);

export default App;

AuthedRoute.js

import React from "react";
import { Redirect, Route } from "react-router-dom";

const AuthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/auth/login",
              state: { next: props.location }
            }}
          />
        )
      }
    />
  );
};

export default AuthedRoute;

UnauthedRoute.js

import React from "react";
import { Redirect, Route } from "react-router-dom";

const AuthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : !isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/"
            }}
          />
        )
      }
    />
  );
};

export default AuthedRoute;

withSession.js

import React from "react";
import { withRouter } from "react-router";

export default (Component, unAuthed = false) => {
  const WithSession = ({ user = {}, streamToken, ...props }) =>
    user.id || unAuthed ? (
      <Component
        userId={user.id}
        user={user}
        session={window.streamSession}
        {...props}
      />
    ) : (
      <Component {...props} />
    );

  return withRouter(WithSession);
};

Login.js

import React, { Component } from "react";
import axios from "axios";

class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      email: "",
      password: ""
    };

    this.initStream = this.initStream.bind(this);
  }

  async initStream() {
    await this.setState({
      loading: true
    });

    const base = "http://localhost:8000";

    const formData = new FormData();
    formData.set("username", this.state.email);
    formData.set("password", this.state.password);

    const registration = await axios({
      method: "POST",
      url: `${base}/auth/users/`,
      data: formData,
      config: {
        headers: { "Content-Type": "multipart/form-data" }
      }
    });

    const authorization = await axios({
      method: "POST",
      url: `${base}/auth/token/login/`,
      data: formData,
      config: {
        headers: { "Content-Type": "multipart/form-data" }
      }
    });

    localStorage.setItem("token", authorization.data.stream_token);

    await this.setState({
      loading: false
    });

    this.props.history.push("/");
  }

  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    return (
      <div className="login-root">
        <div className="login-card">
          <h4>Login</h4>
          <input
            type="text"
            placeholder="Email"
            name="email"
            onChange={e => this.handleChange(e)}
          />
          <input
            type="password"
            placeholder="Password"
            name="password"
            onChange={e => this.handleChange(e)}
          />
          <button onClick={this.initStream}>Submit</button>
        </div>
      </div>
    );
  }
}

export default Login;

Chat.js

import React, { Component } from "react";
import {
  Chat,
  Channel,
  ChannelHeader,
  Thread,
  Window
} from "stream-chat-react";
import { MessageList, MessageInput } from "stream-chat-react";
import { StreamChat } from "stream-chat";

import "stream-chat-react/dist/css/index.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.client = new StreamChat("<YOUR_STREAM_APP_ID>");

    this.client.setUser(
      {
        id: "cool-sky-9",
        name: "Cool Sky",
        image: "https://getstream.io/random_svg/?id=cool-sky-9&name=Cool+sky"
      },
      localStorage.getItem("token")
    );

    this.channel = this.client.channel("messaging", "godevs", {
      image:
        "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png",
      name: "Talk about Go"
    });
  }

  render() {
    return (
      <Chat client={this.client} theme={"messaging light"}>
        <Channel channel={this.channel}>
          <Window>
            <ChannelHeader />
            <MessageList />
            <MessageInput />
          </Window>
          <Thread />
        </Channel>
      </Chat>
    );
  }
}

export default App;

Restart your frontend application and you should be hit with an auth wall! Enter your email and password and a token will be requested and stored in local storage.

Step 7. Sending a Message from the Python chat server

Occasionally, you will want to write to the chat API using your backend Python-based server. Here’s a quick management command that you can use:

Verify that installed apps looks like this in settings.py:

INSTALLED_APPS = [
    'corsheaders',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'djoser',
]

Next, create the directory chat/management/commands. In that directory, add a file called broadcast.py with this content:

from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from stream_chat import StreamChat

class Command(BaseCommand):
    help = 'Broadcast the message on your channel'

    def add_arguments(self, parser):
        parser.add_argument('--message')

    def handle(self, *args, **options):
        client = StreamChat(api_key=settings.STREAM_API_KEY, api_secret=settings.STREAM_API_SECRET)
        client.update_user({"id": "system", "name": "The Server"})
        channel = client.channel("messaging", "kung-fu")
        channel.create("system")
        response = channel.send_message({"text": "AMA about kung-fu"}, 'system')
        self.stdout.write(self.style.SUCCESS('Successfully posted a message with id "%s"' % response['message']['id']))

You can try posting a message to the chat like this:

$ python manage.py broadcast --message hello

And you should see a response like this:

Broadcast

Final

I hope you enjoyed this tutorial on building a chat application with Django, Python and React!

Happy coding! Thank for reading! Please share if you liked it!

This post was originally published here

python reactjs django web-development

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire Python Developers

Are you looking for experienced, reliable, and qualified Python developers? If yes, you have reached the right place. At **[HourlyDeveloper.io](https://hourlydeveloper.io/ "HourlyDeveloper.io")**, our full-stack Python development services...

Hire Python Developers India

Looking to build robust, scalable, and dynamic responsive websites and applications in Python? At **[HourlyDeveloper.io](https://hourlydeveloper.io/ "HourlyDeveloper.io")**, we constantly endeavor to give you exactly what you need. If you need to...

Basic Data Types in Python | Python Web Development For Beginners

In the programming world, Data types play an important role. Each Variable is stored in different data types and responsible for various functions. Python had two different objects, and They are mutable and immutable objects.

Hire Web Developer

Looking for an attractive & user-friendly web developer? HourlyDeveloper.io, a leading web, and mobile app development company, offers web developers for hire through flexible engagement models. You can **[Hire Web...

Top Python Development Companies | Hire Python Developers

After analyzing clients and market requirements, TopDevelopers has come up with the list of the best Python service providers. These top-rated Python developers are widely appreciated for their professionalism in handling diverse projects. When...