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.
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
Top comments (2)
Thanks a lot.
Also, connecting to JS WebSockets did not work until I added
uvicorn[standard]
in myrequirements.txt
file as instructed by an error log on railway.app.Thank you once again for your help!
You're welcome. Also, thanks for sharing this