Hosting django channels with gunicorn + uvicorn + PostgreSQL + redis on railway
Tags: django, channels, redis, postgres
Published: 2023-05-18 21:57:44

Recently, I had to build a REST API for a startup company and during that period, I was tasked to build a web socket for it's chat application. I thought it was an easy job since I had done something similar in the past but then I realized I have never hosted a web socket project before. 

Going through numerous documentation, I started the deployment process with daphne but it was kind of futile. Neither `http` nor `websocket` endpoints were working. After a lot of researching and documentation reading, I was able to get it running. In this tutorial, I'll be giving a detailed explanation on how to host your django asgi project on railway.

I'll assume you already have little knowledge on django and django channels so I won't be doing a lot of explanation on that. Let's start by creating our django project

```
$ mkdir chat
$ cd chat
$ python -m venv venv
$ source venv/bin/activate
$ pip install django channels_redis
$ django-admin startproject chat .
$ python manage.py startapp myapp
```

The app `myapp` will be created in the current directory. Go to `chat/settings.py` and add `myapp` to `INSTALLED_APPS`

```
# chat/settings.py

INSTALLED_APPS = [
    # other apps
    "myapp.apps.MyappConfig",
]
```

Next, on the terminal run the following commands.

```
$ python manage.py migrate
$ python manage.py createsuperuser
``` 

After running the above commands be sure to start the server to ensure your project is running smoothly by running the command below

```
$ python manage.py runserver
```

Visit https://localhost:8000/ to view the project. Next, we'll be configuring our project to use *ASGI*. To use the ASGI server, we'll install `django-channels` and `daphne`.

```
$ python -m pip install channels["daphne"]
```

Once the installation is completed, add the following code to the necessary files.

```
# chat/settings py
INSTALLED_APPS = [
    "daphne" # this should be the topmost app.
    # other apps
]

ASGI_APPLICATION = "chat.asgi.application"

```

```
# chat/asgi.py
import os
from django.core.asgi import get_asgi_application
import django

# django channels
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator

from chat.websocket_urls import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Chat.settings')

django.setup()

application = ProtocolTypeRouter({
	"http": get_asgi_application(),
	"websocket": AllowedHostsOriginValidator(
		AuthMiddlewareStack(
			URLRouter(
				websocket_urlpatterns
			)
		)
	)
})
```

Right now, our setup is partially done, go back to the terminal and restart the server. You should see something like this. If it doesn't look like that, try troubleshooting.

![daphne-server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjvyf646pqs3saxd73ti.png)

Another way you can run the server is by using daphne directly.

```
$ daphne chat.asgi: application
```

To speed up things, I'll provide the code for the consumers and routers. Quickly create two files in `myapp` directory. The files should be named consumers.py and routers.py. add the following piece of code to the files.

```
# myapp/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()
        # other code

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)

    async def receive(self, text_data):
         # piece of code to handle receive events.
```

```
# myapp/routers.py

from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<room_name>\w+)/$",consumers.ChatConsumer.as_asgi()),
]
```

It is not yet done as we still need to use channel layers to be able to broadcast messages. To do that, add this piece of code to `chat/settings py`.

```
# chat/settings.py

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("localhost", 6379)],
        },
    },
}
```

We now have our channel layers setup, message broadcast is now possible. Right up to this point, we have been working on our development environment, let's deploy our application now. The production environment is quite different from the development environment but no need to change much since I already had that in mind from the start.

```
$ python -m pip install psycopg2 gunicorn uvicorn
$ pip freeze > requirements.txt
```

After those packages are installed and the `requirements.txt` file is created, create a file and name it `Procfile` with no extension. This tells railway the start command to use when deploying our application. Add this to the file.

```
web: python manage.py migrate && python -m gunicorn Chat.asgi:application -k uvicorn.workers.UvicornWorker
```

Some changes need to be made to our `chat/settings.py` file but I'll only be going over a few of them.

```
# chat/settings.py
import os

SECRET_KEY = os.environ.get("SECRET_KEY")

DATABASES = {
	"default": {
		"ENGINE": "django.db.backends.postgresql",
		"NAME": os.environ.get("PG_NAME"),
		"USER": os.environ.get("PG_USER"),
		"PASSWORD": os.environ.get("PG_PASSWORD"),
		"HOST": os.environ.get("PG_HOST"),
		"PORT": os.environ.get("PG_PORT"),
	}
}

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [(os.environ.get("REDIS_URL"))], # update this line.
        },
    },
}

```

With these few changes, we are ready to deploy our channels application to railway. Go to the railway website and create a PostgreSQL project and a Redis project. Once that's is done, create a django project by selecting the project from github(you should have pushed your code by now). Go to variables and set all the environment variables we used in `settings.py` and other variables in your project. The environment variables for PostgreSQL are in the PostgreSQL project you created, same for the Redis environment variable. Also, you should most likely set the `DJANGO_SETTINGS_MODULE` variable to your project's `settings.py` file. After this is completed, your project should now be live.

In case of any problems you encounter during deployment, kindly state that in the comment section and I'll be glad to help