2023-05-17 Writing a chat application in Django 4.2 using async StreamingHttpResponse, Server-Sent Events and PostgreSQL LISTEN/NOTIFY

With the release of Django 4.2 we got the following:

StreamingHttpResponse now supports async iterators when Django is served via ASGI.

And the documentation has been expanded with the following:

When serving under ASGI, however, a StreamingHttpResponse need not
stop other requests from being served whilst waiting for I/O.

This opens up the possibility of long-lived requests for streaming
content and implementing patterns such as long-polling, and server-sent events.

Being a sucker for simplicity I got quite intrigued by the possibility to serve server-sent events (also known as SSE) from Django in an asynchronous manner.

So I set out to write a small, drumroll please, chat application!

This blog post documents my process of writing this application and how the bits and pieces fit together.

The code for the chat application can be found at github.com/valberg/django-sse

Conclusion

Django is boring, which is a good thing, to the degree where it is always the safe option .

But with the advances in async support it is becoming a viable, and shiny, option for doing real time stuff.

Mix in some other solid and boring tech like PostgreSQL and SSE, and you end up with a very solid foundation for building real time applications .

Appendix

How to run ASGI applications in development

One thing that took me some time to realise is that the Django runserver is not capable of running async views returning StreamingHttpResponse .

Running the view with the builtin runserver results in the following error:

.../django/http/response.py:514: Warning: StreamingHttpResponse must
consume asynchronous iterators in order to serve them synchronously.
Use a synchronous iterator instead.

Fortunately Daphne, the ASGI server which was developed to power Django Channels , has an async runserver which we can use:

To set this up we’ll have to install the daphne package, add daphne to the top of our installed apps, and set the ASGI_APPLICATION setting to point to our ASGI application

INSTALLED_APPS = [
    "daphne",
    ...
    "chat",  # Our app
]

ASGI_APPLICATION = "project.asgi.application"

Now we can just run ./manage.py runserver as before and we are async ready!