Deploy Django: GitOps & Kubernetes Made Easy with Calvin Hendryx-Parker
Published October 23, 2025
This video features Calvin Hendryx-Parker at DjangoCon Europe 2021 in Online.
Django Channels extends Django’s traditional synchronous request-response model with ASGI, WebSockets, channel layers, consumers, and lightweight background workers. Calvin Hendryx-Parker explains how LoudSwarm uses WebSockets to push Slack and Discord messages to browsers, including a custom Channels worker that starts and maintains an outgoing Discord connection while saving messages through the Django ORM. He stresses that Channels workers are at-most-once delivery, so they suit work such as thumbnail generation but not tasks requiring guaranteed processing. He also covers routing, bidirectional messaging, and the need to account for per-connection memory and channel-layer capacity when scaling.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Uh really appreciate being here at DjangoCon Europe, uh even though it is like quarter to five in the morning. Uh I got all pretty up for everybody so we could uh talk about uh Django channels.
Speaker 2: Uh
Speaker 1: I think actually someone needs to mute.
Speaker 2: I don't okay. I think
Speaker 1: Java needs you need to mute. I can't mute you. David, I need someone to mute over there. The gosh is
Speaker 3: the layer. Gadgetho.
Speaker 1: Ah, there we go. All right. Awesome. Now we're now we're uh rolling. So welcome to uh DjangoCon Europe. Uh my name's Calvin Hendricks Parker. I am co-founder and CTO of Six Feet Up, but I assume you all know that because you've probably seen the um the little pre-roll ads. But I'm excited to be here and talk about hacking Django channels for fun and profit. This is something that is actually really really relevant for what we're all doing here right now today because we're using it. In the Loudswarm platform. So I'll talk a little bit about backstory and where we're going and how we did all the things we're doing. There's a later talk today where I'll talk more about the cloud infrastructure that makes up Loudswarm. But this talk specifically is going to be about the under the covers of how we're using channels to do some cool web
Speaker 1: socket stuff. So let's get rolling here. First a little bit of backstory just to kind of set the context. Back when Django started life and 2005, it was a synchronous web application server because really nothing else kind of existed in the space. It was definitely cutting edge for uh what it was at the time. But async is becoming more and more part of Django with each of the new 3. x releases But to understand that that difference between the synchronous web and the asynchronous web is really important for understanding the modern web applications that we're building today. So if we talk a little bit more about what the Django request response loop uh looks like, you can see that we're
Speaker 1: um This is this this is the standard Django like request and response. So when requests come in, they go through all the various like middleware that you configure in your settings, stockpy. And then they get hit your view function and then they actually go back through the middleware on the other side out. So this is a request and then there's a response. So it's all synchronous, happens just as it comes in, just as it goes out. This is how Django is going to be processing those. There's actually a really good view video here that describes that in like much, much, much greater detail. And this is all done synchronously since we're living in WSGI land or WISGI land. So WSGI, when you hear that term, is going to be the synchronous part of Django. But let's actually talk about the asynchronous part of Django because that's why you all are here is to talk about asynchronous and not necessarily synchronous.
Speaker 1: So let's make this actually work by directional link. So normally when you want to uh establish a connection to some kind of a for example if you're establishing a connection to a database that's like a long-running connection we will want the same thing for our web application to be able to talk to Django. We'd want to be able to establish some kind of a connection that we can make requests down and get responses from, but the opposite can happen. The web application itself can take and send requests from the application. to our browser and provide some kind of interactivity. So that is where we get the difference between ASGI and WISG WISGI, WSGI. So the ASGI is an asynchronous gateway interface, and that's what allows us to do this bi-directional. So in Django, which is kind of cool, you can just change out uh WSGI Pi for ASGI Pi.
Speaker 1: There's a really nice guide there from ARun who talks about Django 3. 0 and the performance and moving into asynchronous. And also yesterday Amanda did a really great talk with an intro to channels and she talked specifically about this actually coded through the process of building a sample chat app where she used ACI and I'll talk about the router and setting up the routings. But you know, this whole ASGI vs WISGI can really be its own talk. But ASGI is a superset of WSGI and can even call WSGI callables, and I'll show that in a code example uh later. But prior to Django 3, Django channels provided the ASGI asynchronous support to allow for those long-running uh connections like WebSockets or MQTT and more.
Speaker 1: And this allows us to use a fully asynchronous event loop inside of the Django application server, which was historically synchronous, but as of three has grown these nice asynchronous features. And just as a note, each version of of Django three since three oh has added new asynchronous features. So like for example in three one One three, we got the the uh routing layer, and three one we got views. Uh three two has added a few more bits in there. We're supposed to add in the asynchronous um ORM, but that's not quite ready yet. So that'll be coming very soon to a Django near you. So that's what that's the kind of back inside of Django. How does that back inside of Django talk to the browser, which is basically the consumer of your application?
Speaker 1: This is where we come into using WebSocket. So if we want to see WebSockets in action, let's actually, I'm going to give a quick little demo here showing that in action. So if I click over , you may recognize uh this screen since you're watching it right now. Uh luckily I don't have the audio on, so you shouldn't hear. me but the the the web application for loudswarm uses web sockets in uh one sp very specific area right now that you can see that's visible to the end users. If you look at this uh track Slack box down here. These messages are coming real time pushed from Slack over into the web application using WebSockets. And we can see those sockets if you open up if you open up the web inspector If you scroll down here, you'll see I've got the WebSocket
Speaker 1: showing in the network tab of my web inspector. And here you can see the various like messages. So a lot of these were people clapping. You can see here there was the preview message coming up, you know, hacking Django's and Django channels for fun and profit. I didn't have to reload my page for those messages to come into the page. The WebSockets pushed them to me. And so actually, um If someone was brave enough, they could actually like. Yep, there we go. So you can see new messages coming in in real time against this socket, and they would just show up here as people were chatting into the Slack channel. So that's that's actually what the WebSockets are doing for us, is giving us that bi-directional um access. In our case, we're using it from Django to the web browser as opposed from the web browser to the Django to the to the Django, but you can actually use that to enable all kinds of really rich uh
Speaker 1: UIs uh and applications where the web can now feel like an application and not so much a request and response, you know, full back and forth um uh stateless type of a transition because http is we all know is is a stateless protocol means that every request is like a whole brand new world it's like the it's like the web had amnesia And every request I make is going to be a brand new request out to the world. But WebSockets allows us to establish some manner of state back to the server. So channels is the Django component, as Amanda showed yesterday in her awesome talk, that adds in the inbound web sockets. So Our browser can connect to Django channels, subscribe to a consumer, and now messages can be sent back and forth over that channel
Speaker 1: over WebSockets technically. Really, what it does is you know it wraps asynchronous views. Uh so we now have that ability to do that, keeping our full async event loop and everything going. Uh so that's actually well kind of now where things come in is channels. So Django Channels offers us three main components that help us build these rich web applications. The main one that you'll typically interact with, which was shown yesterday in the tutorial, was the consumers. This is what we typically think about when we talk about channels and WebSock. You would write a consumer that would listen for various kinds of messages coming either in or going out. Now you can send messages over the channel layers. uh the the consumers is just kind of the easy way we interact back and forth between our application and the
Speaker 1: the well the browser and the web application on the back end. The other two pieces that we don't typically think about when we're interacting as a developer with channels if we're not dealing with kind of deep back of the stack type things is going to be channel layers and the background markers. So when we use channel layers, that's when you send a message to the consumer to send something to your web browser or the socket. And then the last one is actually what's going to be really interesting for us. is the background workers. I don't remember when this feature was exactly added into channels. This allows for very lightweight background tasks, kind of like a celery. So if you're familiar with celery. You can create tasks that run asynchronously in some worker someplace. Here we can create a worker that's a little lighter weight, but doesn't have all the robust features of Celery.
Speaker 1: and actually run them straight in Django without having to add in the whole salary package. Uh it creates a a background worker command that you'll run to run your background tasks. For example, when doing something on these background tasks, I'll talk about this a little bit later, but you can do things like generate thumbnails asynchronously as folks maybe upload photos into your imaging site But let's look at the consumer real quick. This is an example of a channels consumer. You can see that it has a Connect and disconnect, so there's a basically a simple API where you implement these methods on your class for your consumer And so in our case, you know, we we accept the connect, we give a connected equal true. If you're actually playing along at home and watching uh the WebSockets.
Speaker 1: in the LoudSwarm application, you would see in your JavaScript console like these connected true messages or on the network tab, these connected true messages coming in. Disconnect provides you a way to clean up or do some kind of closing activities once the web socket closes. What's more interesting probably is going to be the receive JSON. So if we set up a receive listener, this is going to process things coming from the browser, and you can then make decisions on how to handle that. We'll talk about sending messages to a consumer later, or so you can actually send messages to the channel layer so that those can get back to the browser. That's just basic basic JSON Web Talk at Consumer channels makes it really, really easy to implement these and add this kind of rich interactivity to your applications.
Speaker 1: How do you get this all hooked up into Django itself? Well, we want to be able to hook into the Django routing. Uh since actually there's a really good talk. Uh I think it's Andrew Godwin from 2019, DjangoCon US. where you talked about you know adding in asynchronous features to uh Django. When you ask Django for uh uh a URL, it's going to respond with some kind of routing. So you all are familiar with the routing. py and how you can specify URL patterns. When you start going into the asynchronous world, you're going to need to handle multiple types of protocols. In our case, we're going to have the standard HTTP protocol. So as of channels three, you need to make sure you specify that HTTP. uh protocol type router so that it can hand that off to the standard Django URL routing mechanisms.
Speaker 1: But we also want to be able to handle WebSockets so when our browser makes a web socket can Connection back to our application, it can route that off. In this case, you see we're wrapping a middleware around our URL router for our specific application's URL patterns And there's a last one in there which you're probably not familiar with is you can actually have a channel named router. And in this case, we're gonna ask, we're gonna make it's basically channel views where we can now establish WebSockets, persistent WebSocket connections. from Django to another application. So this is where the this is where things are starting to diverge from the normal Django channels talk. is normally we're talking about a chat application or some kind of a simple you know web socket connected to the web browser.
Speaker 1: This is not a web browser talking to Django. This is Django talking to another WebSocket service and establishing a persistent connection outgoing from Django to something like, for example, Discord. So we're going to talk about how that works. So in this case, we're using a Discord to be able to listen to a Discord guild uh specific channel and be able to grab messages in real time as they're happening uh in discord. So there's a big difference between how we do Slack and Discord in Loudsform. The Slack integrations are not using a WebSocket connection. They're actually web hooks that are coming from Slack. They hit an endpoint on Django, which is just a standard Django view, and then that actually triggers a layers, a channel send. to get that message all the way to your browser, the
Speaker 1: the Discord integration mechanism is different in the case that Discord integrations are done via WebSockets. So we actually get to use fully asynchronous all the way through with WebSockets from Discord to your browser. So we're just kind of picking those up and putting them in the right place and making sure that everything works great here. So let's talk about the background workers. This is where we can try and establish those channel connections to Discord, for example, or normally, you know. If we think of what I talked about earlier, the the channels consumer where a web browser is talking to Django. Background workers are new to channels and And I guess a little less used and maybe not getting the uh the airtime they need. So I'm hoping to expose some of you all to how we can use background workers
Speaker 1: to do some interesting work. Yeah. Um, if you look in the docs, there's an interesting example there around generating thumbnails with a background worker. So you can actually, as part of your code, maybe you've got a signal that would send a layer message to a specific channel view, not a Django view or any other kind of views inside of the synchronous Django bits, we would actually kick off a worker to do some kind of work in the background. Uh but that's normally like Django initiating a a call to a channel layer and doing some work. What if we really wanted the reverse? What if we wanted You know, a web browser establishes a bi-directional channel to our apps, one thing, but what if we wanted to establish that channel connection to some other long-running service? Like I mentioned with our Discord.
Speaker 1: This is where we pull in and actually use that background worker in channels. And let's talk about what we can do with that a little bit here. Is that we want to sit and listen for messages on a channel layer and then do some work. That's what channels background workers normally do. What's nice about the channel's implementation as opposed to using Celery as I mentioned a little earlier, is that it is much lighter weight. You're not bringing a whole bunch of extra machinery along the way. The channel workers are very, very lightweight, very, very uh fast, very easy to use. But there is a caveat as a like a little note here, kind of the beware. They are an at-most once operation. I'll let that sink in a moment because you have to be careful and you have to design your application with that understanding in mind.
Speaker 1: Uh we want to make sure that we're not losing messages if they are important. If that's if if your application depends on guaranteed delivery and maybe one-time processing. There are other Q technologies that are more well suited to handling this. You know, that's why we have Celery, and Celery is still a use case that we use in Loudswarm because we have different kinds of applications that need different kinds of needs An atmosphere once operation works when something can be lossing. So for example, like I mentioned with the thumbnail generation You can have it do that in the background to try and offload from the main Django application so it doesn't have to generate those synchronously. So the user's experience can be a lot can appear or be perceived as a lot faster because we can do those things in the background.
Speaker 1: We're not worried about generating a thumbnail on an upload synchronously at the same time, which is what we would have done kind of pre-Django 3. pre -channels is you would have had to kind of wait for that process to finish running or you would have had to use celery to process those later. But celery may be a little heavyweight for this and also we may be okay with it not happening right away. It could be that on a some request for that specific image, we could generate the thumbnail on that at that point in time if it didn't exist. So we we can kind of work with the system and allow us to you know use this lightweight mechanism. So that's the the generating thumbnails example. Let's talk about the real world use case we're seeing here, which is our Django um Discord chatbot. So but built inside of Django.
Speaker 1: There's lots of great example chatbots for Discord written in Python out there. And why didn't we just use one of those? Well For us, having Discord tightly integrated with the chat mechanism is important because we want to be able to use things like the Django ORM to save those messages off into the database once they come in. So, you know Discord requires me to talk asynchronous to it and use a web socket to send and receive. I mentioned how we do Slack using the webhooks, but You know, why is it important that it's inside of Django? In our case, I'll tell you, is that the batteries are included. I want to use all the awesome stuff that's included inside of Django, and the big one here is I want to use the ORM. I want to be able to make sure that it's just super easy for me as my as an application developer
Speaker 1: to save data into the database without going outside of of Django itself, you know, writing my own SQL methods or whatever it is to talk to Django from an external process, I can actually integrate this into my development workflow and actually have it all be part of my Django application. Now, the issue is we want to do something that channels can do, but doesn't do out of the box. Uh if you refer back to my uh the the routing example. where we had the HTTP, the WebSocket, and the channel router. When you make a channel error request to it, the channel protocol, It's just like a view. You're gonna have to call it if you want to kick something off. So you could write a consumer
Speaker 1: and uh ha attach it to that route for a channel uh protocol, but something would have to kick it off. So the the the the whole bit that doesn't work and the whole reason I'm probably talking today about this is because this is one thing that channels doesn't do out of the box but could do. You can actually You know, I was trying to we but basically the problem was on Django's start, how do I get it to kick off a persistent connection to Discord? That that's the total like problem statement there. Uh and the the answer is you're gonna have to hack uh the channels bit a worker background worker a bit. So this is what we're going to talk about here is we we can do this by making our own version of the run worker. So we basically fork, well, we just copied over and and overrode
Speaker 1: the handle method on the background worker. And this is the the only code we changed about that background worker to really get the kind of this is the key bit of how this all works. And what what is working here, and I'll kind of point it out, if we look, we've added in for lack of a better term, uh a list of lackeys, which are going to be tasks that we would want to set up to work in the background. So we we get those for example, we wanted to create an outgoing connection on to Discord API, we set up all these keys, uh all these uh tasks in our background worker and then we kick them all off and then we wait for results. uh if they for example if they air out or or if they exit at some point.
Speaker 1: And there's two kinds of things we can do here and I'll talk about this a more in uh as we go on kicking off a long running process this also allows us to set some kind of on start tasks for Django Which could be an interesting use case that maybe when a Django worker starts up or when a Django process works starts up in your cluster, there may be some startup tasks you wanted to do, but it's never again very easy to to set that up and have it work. um out of the box. But Django Channels run worker actually allows for that very, very easily. So by adding this little bit of code into our default worker method for Django channels, we've actually been able to now kick off or an you know, start an asynchronous process uh right at Django start time.
Speaker 1: So again, this helps us turn that standard Django channels concept a bit inside out where instead of waiting for a request to a channel layer, we can Initialize that right on right off the top. So if we talk about some examples, I'll dump and dive into this a little deeper. So when we run that worker We're going to start up our chat client. So we we create our own Discord chat client and then kick it off so that it starts listing for those messages. So this is just a standard um actually if you use Discord. py I want to give a big shout out to the Discord Pi project. What an amazing set of documentation library and APIs. It makes it super easy for you to start building. Sorry. I try to remove the word easy from my uh vocabulary uh because developers shouldn't say anything, it's just super easy, but I'll say it's very convenient as a developer to use Discord
Speaker 1: Pi to build Discord applications. And using it inside of Django is no different. We just have to think in an asynchronous fashion, kind of different from what we've been doing from previous versions of Django. So we build our chat client. In this case, we just. you know, super subclass from the Discord client. We listen for messages, uh, we set up data, and then we just wait for uh that data to come in. So as new messages roll off, like on messages inside those channels, we set this up and then we just pass that off into another um method that calls into the ORM And saves our data into the database for us to send then, well, does two things, saves it in the database, and then it also sends a WebSocket
Speaker 1: connection to your browser so you get the Discord messages in your browser. So that's how that's all hooked up. So pretty straightforward. That's like literally all the code there is for making the chat client part of the Discord. Now what happens on the sender? uh if we want to send messages to discord, so we kind of got bi we have bi-directional here. If if there's messages coming in, we would actually want to send those messages, for example Uh you'll see the conference messenger inside of Slack right now sending the updates saying coming up in four minutes, you know, here's the next talk, next speaker. we want to be able to support that behavior as well. So we still have the Discord sender, which is a consumer, just like a standard channel is this consumer, except instead of talking to our browser, it is talking to the Discord services in the cloud.
Speaker 1: So we just set up a new message and then do the channel dot send. So we get a nice and easy using channel. We're using channels both directions here. Sending and receiving messages, you know, this sends over that same client. So you can see we're using our our Discord client or our Discord worker to send those messages. And this is how we send those messages. So once we have a task to send a message out, so for example, we use Celery, scheduled task, to look for all sessions that are coming up in the next five minutes. It crafts a message and just calls Discord send and it's away it goes. But you can use this for any long-running task, which is pretty awesome. Uh you if you've got specific needs and you want to be able to run a long-running asynchronous task in the background,
Speaker 1: the channels workers are really, really well suited. uh to this type of application and I highly recommend it because it's really easy to work with and it feels very much like you're working in Django. So if you really love the Django framework and you love the feel of it, I recommend, you know Checking deeper into the background workers of Django channels because it's pretty awesome. Now, what I want to see next. I would love to get this added into the Django 's code base with some examples and documentation and tests. So that's kind of our next step. We're working on that right now. It's not a big code change, but in open source, you've got to be a good open source community member. I want to make sure we do it correctly and have tests and everything so that it can all go. I think giving more examples of long running processes and single shot coroutines would be good.
Speaker 1: Sometimes it's hard to conceive of ideas or why this is useful. And I think seeing examples is important. So working on the documentation for us to put this out there is is also coming up next. And then we want to add in the option for uh two kind of classifications of these coroutines. You know, one's a start right away, uh so like a long-running Discord connection to Discord servers, for example. and ones that can run and then or or ones that ones that can run if they stop. So you've kinda got three classifications though. There's things that can run right away and stop. There are things that are long running and will continue to run until you stop the server. And then things that can run after as a cleanup mechanism to your long running process. So for example, if we were running in the Discord long running server.
Speaker 1: and the Django server was getting ready to shut down or there was a you know the socket closed, we would actually want to be able to schedule certain kinds of tasks to actually run in response to sockets closing or the Django server getting ready to shut down. So that that's coming up. Now right now we've only focused on our specific need, which was to create long-running tasks that can run and talk to Discord. I'll leave you all with uh one final kind of tip and trick and then I'll kind of check out I've not looked in the questions here yet in the Slack channel, but I'll check those out in a moment that Something that's not in the docs, but probably should be, you know, maybe I need to make a poll request here is when you're dealing with channels and web sockets, you have to think about scale. Uh one thing that's pie
Speaker 1: not obvious as a new developer to channels is that every person who logs into your WebSocket is establishing that persistent connection back to your web server or to your wherever you're running wherever you're running your application. Each of those connections requires memory. So there's some simple calculations you can do. You can look at how much memory each new connection takes. But you have to also take into consideration that channel layers, the the layer below the consumers, also has a a default capacity where it will start queuing up Uh well it actually doesn't queue up. It will it'll basically has a threshold and after that it starts dropping messages. Remember how I said there's a at most once delivery? Uh well this we ran into this um with this specifically
Speaker 1: Is that your channel layer capacity defaults to 100? If you start pushing more than 100 messages at a time, or if the that level starts growing above 100 Uh they will drop silently and you'll be scratching your head as to why. For our application we changed it up to a thousand with no adverse effects. on memory. You just have to we will have to watch your memory because as you start tuning these numbers up, obviously there'll be more resources used. So make sure you are aware that that channel capacity is really like a It's a the it's a it's a I can't what you call it what the concept is, but basically as soon as you reach 100, it's gonna start dropping them silently and you you won't get those messages. I mean you can still keep sending more messages into it and it's going to keep processing them as it can get them, but it's only going to process the hundred
Speaker 1: that are sitting there right now. So you'll want to up that number. It's something you can set in your settings file to get that channel layer capacity up. To over a hundred. And with that, I want to say thank you all. I want to thank the uh DjangoCon Europe organizers. Uh I really enjoyed this conference and been I'm super excited I was able to be a part of it as a speaker this year. Let's see if there are any questions over in Slack.
WSGI is Django’s synchronous gateway interface, while ASGI is its asynchronous counterpart that supports persistent, bidirectional connections such as WebSockets. ASGI is a superset of WSGI and can also run WSGI callables.
Discussed at 3:27Channels provides consumers for handling messages, channel layers for passing messages between parts of the application, and lightweight background workers for asynchronous tasks. Consumers are the part developers most commonly use to connect an application with a browser or WebSocket.
Discussed at 8:15Channels background workers are useful for lightweight asynchronous work, such as generating thumbnails, when occasional message loss is acceptable and Celery would be excessive. They provide at-most-once delivery, so Celery or another queue is more appropriate when guaranteed delivery or reliable one-time processing is required.
Discussed at 15:12Channels does not start such a connection automatically when Django starts, so the speaker modifies or subclasses the Channels background worker and overrides its `handle` method. The custom worker launches the Discord client as a long-running asynchronous task and waits for it alongside any other background tasks.
Discussed at 19:07A Discord client listens asynchronously for incoming messages, saves them through Django’s ORM, and sends them to the browser over a WebSocket. Outgoing messages use a Channels consumer and the Discord client to send data back to Discord.
Discussed at 23:05Channel layers have a default capacity of 100 messages; when that threshold is exceeded, additional messages can be dropped silently because Channels uses at-most-once delivery. The capacity can be increased in settings, but doing so increases memory usage and requires monitoring.
Discussed at 27:42Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published October 23, 2025
Published September 19, 2025
Published November 22, 2023
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025