Hosting django channels with gunicorn + uvicorn + PostgreSQL + redis on railway

codewitgabi

codewitgabi

Posted on May 18, 2023

Hosting django channels with gunicorn + uvicorn + PostgreSQL + redis on railway

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


Enter fullscreen mode Exit fullscreen mode

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",
]


Enter fullscreen mode Exit fullscreen mode

Next, on the terminal run the following commands.



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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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"]


Enter fullscreen mode Exit fullscreen mode

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"



Enter fullscreen mode Exit fullscreen mode


# 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
            )
        )
    )
})


Enter fullscreen mode Exit fullscreen mode

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

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



$ daphne chat.asgi: application


Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode


# 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()),
]


Enter fullscreen mode Exit fullscreen mode

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)],
        },
    },
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.
        },
    },
}



Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
codewitgabi
codewitgabi

Posted on May 18, 2023

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

Sign up to receive the latest update from our blog.

Related