Python Chat Tutorial with Django and React
Tags: chat, python, django, react
Published: 2019-05-08 16:53:30

---
cover_image: https://i.imgur.com/uVPVe7k.png
title: Python Chat Tutorial with Django and React
published: true
description: Build chat with Python, Django, React and Stream Chat
tags: chat, python, django, react
canonical_url: https://getstream.io/blog/tutorial-build-chat-with-python/
---

**This tutorial will explain how to build a chat application with Python, Django and React.**

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](https://getstream.io/chat/?ref=dev.to) that takes care of WebSocket connections and other heavy lifting using Go, Raft and RocksDB.

**Table of Contents**:

1. [React Chat Demo UI](#step-1)
2. [Django/Python Setup](#step-2)
3. [User Auth](#step-3)
4. [Django Rest Framework](#step-4)
5. [Generating Tokens to Access Stream's Chat Server](#step-5)
6. [Integrating Auth in React](#step-6)
7. [Sending a Message from the Python Server](#step-7)
8. [Final Thoughts](#final-thoughts)

> _The GitHub repo for the code below can be found at https://github.com/GetStream/python-chat-example._

Let's code! 🤓

## <a name="step-1"></a>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:

```bash
$ 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:

```javascript
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!

## <a name="step-2"></a>Step 2 - Django/Python Setup (skip if you already have it)

Make sure you have Python 3.7 up and running.

```bash
$ 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:

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

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

```bash
$ python --version
```

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

```bash
$ pip install django
$ django-admin startproject mychat
```

And to start your app:

```bash
$ cd mychat
$ python manage.py runserver
```

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

![Django](https://i.imgur.com/74B13x3.png)

## <a name="step-3"></a>Step 3 - User Auth

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

```bash
$ 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](https://i.imgur.com/4S1RUlg.png)

## <a name="step-4"></a>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](https://github.com/sunscrapers/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:

```bash
$ pip install djangorestframework djoser
```

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

```python
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:

```python
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, have a look at this:
https://djoser.readthedocs.io/en/latest/sample_usage.html

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

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

## <a name="step-5"></a>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):

```bash
$ python manage.py startapp auth
```

Install stream-chat:

```bash
$ pip install stream-chat
```

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

```python
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:

```python
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:

```bash
$ python manage.py migrate
```

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

```bash
$ 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.

## <a name="step-6"></a>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:

```bash
$ pip install django-cors-headers
```

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

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

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

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

```python
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:

```bash
$ 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**

```javascript
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**

```javascript
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**

```javascript
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**

```javascript
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**

```javascript
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**

```javascript
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;
```

Be sure to replace `YOUR_STREAM_APP_ID` with a valid Stream App ID which can be found on the [dashboard](https://getstream.io/dashboard/?ref=dev.to).

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.

## <a name="step-7"></a>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`:

```python
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:

```python
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:

```bash
$ python manage.py broadcast --message hello
```

And you should see a response like this:

![Broadcast](https://i.imgur.com/cR7240Q.png)

## <a name="final-thoughts"></a>Final Thoughts

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

For an interactive tour of [Stream Chat](https://getstream.io/chat/?ref=dev.to), please have a look at our [API Tutorial](https://getstream.io/chat/get_started/?ref=dev.to) on the Stream website. If you are interested in digging into the code for Stream Chat React Components, [the full docs can be found here](https://getstream.github.io/stream-chat-react/). If you are interested in building chat on top of Stream, we offer various [SDKs](https://github.com/GetStream/stream-sdks) for popular languages and frameworks with our latest being [iOS (Swift)](https://getstream.io/tutorials/ios-chat/).

Happy coding! ✌