Django on the Med - Paolo Melchiorre
Published October 20, 2025
This video features Carlton Gibson at DjangoCon US 2024 in Durham, North Carolina, USA.
The last couple of years seem to have changed everything. Particularly with HTMX, but also related technologies like Alpine.js and Tailwind CSS, we've rapidly gone from a world where seemingly the only option was "API First".
This is the story of bootstrapping a new application in these latter days. On a core of almost old-school Django combined with HTMX, with a just sprinkling of Alpine.js, we've been able to build a rich and interactive web application, with hardly a JSON response or payload in sight.
We'll show the integration patterns we've learnt, and what the limits of those might be.
Finally, we'll look at what the future might bring. As we grow the application we're looking whether we will need edge computing, offline, and richer behaviour purely on the client. Is that the limit of the hypermedia driven approach? Is that where we need an API? It's not clear: it's still very much "API Maybe".
This talk was presented at: https://2024.djangocon.us/talks/api-maybe-bootstrapping-a-web-application-circa-2024/
LINKS:
Follow Carlton Gibson 👇
On Mastodon: https://fosstodon.org/@carlton
Website: https://noumenal.es
Follow DjangoCon US 👇
https://fosstodon.org/@djangocon
https://x.com/djangocon
Follow DEFNA 👇
https://www.defna.org/
Video production by Confreaks
Follow Confreaks 👇
https://confreaks.com
https://x.com/confreaks
Carlton Gibson argues that a modern Django application does not need to begin with a separate REST API and JavaScript single-page frontend. For his bootstrapped product, he combines Django models and server-rendered templates with Neapolitan CRUD views, HTMX, template partials, Alpine.js, and Tailwind, keeping behavior local while code is still changing and extracting abstractions only when patterns stabilize. This approach let a small team reach a viable product quickly without duplicating form and rendering logic, while future needs such as collaboration, latency, and integrations can be addressed from evidence rather than by adopting complexity in advance.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Hello. Right, you're still awake then, just Uh yeah, hang on. Wrong button. That button. Yes, right, this is me, you know me. Um Carlton Gibson, um, GitHub Foster Dom. I've got a website, do a podcast with him. Go check that out. Um For those of you who don't know me, between 2018 and 2023 I was one of the Django Fellows. Now I don't have to tell you what Django Fellas are because Natalia did that this morning. The bottom line is that after five years of being the fellow, I stepped down to get back to building things with Django rather than working on Django. So since 2023 and ongoing, I've been working on an application called PARS, which is Monitoring, Evaluation, and Learning for Development and Sustainability Projects.
Speaker 1: That's the Uber for Development and Sustainability Projects. projects. I meant to say that we're hiring, but we're not hiring yet. We're busy bootstrapping. We're a year and a half in and we it's all going very well but This next year is going to be the one that tells whether it sticks. So if I if we get through that, then yes, we might be hiring. So perhaps talk to me at DjangoCon 2025 and we'll see about that. Today's talk is called Applic API Maybe. It's about bootstrapping an application circa 2024, specifically our application. In 2018, when I started fellowing, it was I was working on DRF, Django Rest Framework, and the world was very definitely API first. It was maybe even API only. You'd work on a team with a very clear division between the back end that was built by one group of folks, and then they would expose a REST-like JSON API, and then the front
Speaker 1: end, which would be built by a totally different group of people, would consume that with whatever JavaScript framework of the moment. And that was quite often the API's only client. Now you might be sat there at the back and going, hang on, it's still exactly like that. But spin back to 200 spin forward to 2024 and things have changed a bit. Largely in response to the seemingly ever more complexity in front-end, we've seen a return to a focus on rendering HTML on the server and the emergence of a family of related front-end frameworks that go under the monkey of HTML over the wire. Now, HGMX is the one that's probably got most of the mind share in the Django community, but um we've had plenty of talks on that this week, right? But Turbo, Umpoly, and others are doing similar things in a very similar vein.
Speaker 1: But something else changed between 2018 and 2024 as well, which was where did all the money go The great inflation that followed the pandemic, the global supply chain crisis, and the Russian invasion of Ukraine marked the end of the zero interest rate environment we'd, let's say, enjoyed in the decade or so since the last financial crisis. You've all felt it. Tech layoffs are in the news every week, it seems. But what that means is you can't just go out and pick out a big pick up a big bag of cash to pay for a multidisciplinary team to build up your app from day one. On paths, we have a very limited budget. We've had tight milestones, like 12 weeks to get from a proof from idea to proof of concept, another 12 weeks to make that into an MVP that clients could actually use. a few months only to flesh out the supporting bits like versioning and role
Speaker 1: paste permissions and all those other things. And then we so we didn't have cash for a big for a big team. It was just literally me on the technical side. And there's no way that I could do all of that if I'd had to take on Django and a big front-end framework like React Right, so my contention is that by leaning into Django, you can get just as much done as bigger teams using more complex stacks, and that Django really shines exactly when the money is tight. Django has always been the framework for perfectionists with deadlines. When those deadlines are financial, all the more so. Django is the perfect web framework for our post-zero interest rates world. But first, well, I just want to talk about something called locality of behavior.
Speaker 1: This is an idea from the creator of HDMX, Carson Gross. Locality of behavior is a way of thinking about and assessing code. Some code has locality of behavior when everything you need to understand that code is there in front of you in one place. The contrast is when you need to go and have a look somewhere else to see what the behavior is going to be. The idea is that other things being equal, greater locality of behavior makes your code easier to reason about, easier to maintain, and easier to iterate on. Now again, you'll be familiar with this. Tailwind CSS is a utility-first CSS framework that's very popular. Gives you utility classes that you embed in your HTML to control your styling rather than needing to keep that code in a separate CSS file. Alpine. js is a rugged little jQuery-like JavaScript framework that lets you define your component
Speaker 1: behaviors again in your HTML rather than in a separate JavaScript file. And of course, HTMX adds a small set of HTML attributes that expand the native powers of HTML all again without leaving your HTML. Karen, Chris, Josh, and others have talked about these tools already in this conference. On top of those behaviours, both in the name of um locality behavior. I've suggested Neapolitan, which is my take on quick crud views for Django, and Django template partials, which adds reusable name template fragments to the Django template language. I suggested that particularly with new code, while you're still working on it, whilst it's in flux, a focus on locality of behavior can help you go faster. But I think we need to put up some
Speaker 1: disclaimers about locality of behaviour. Let's be 100% clear. Locality of behaviour is a starting point, not a destination. If I've got a button styled with Tailwind, I'm not going to mindlessly copy and paste those classes every time I need a button I might stick the HTML in a partial or in a template tag, but much more likely I'm just going to pull all those classes into my CSS file and create a new button utility as anybody would obviously do. Alpine. js is awesome. A one-line handler in your HTML file, beautiful. Two lines, no problem. At any given moment, one more line of JavaScript in your HTML file is not a problem. But at some point and probably quite soon
Speaker 1: you're going to pull your JavaScript out into a separate file or into an Lprime plugin so that you can use it with just an attribute declaration on your HTML. You're going to get linting, formatting, testing, and all the rest of it. Right? That was what Karen was talking about yesterday. You're going to use your judgment as to when you sacrifice a bit of locality of behavior as your code scales to gain in maintainability. If you're using Neapolitan's crud view, you might see that, oh yes, this logic would be better now as a separate view, and so on. Locality of behavior doesn't mean that we abandon the good engineering practices that we've learned since we were juniors. It's a tool, not an end in itself. Right? Just a moment. I like water when I'm talking.
Speaker 1: But I will say holding the line on locality of behavior can really play dividends. Dry, separation of concerns, yeah, sure, sure, sure. But for every one of those there's a write it twice and a three times refactor. As I was writing this talk, Luke Plant published a nice post compar comparing these pro these programming mantras to Proverbs. They're great when they apply, but for every one of them, there's always a contradictory one if you need to pull it out. Again, we need to be sensitive with these things. A principle that I learned as a junior was called protected variations. The idea was that you identify areas of your code that are subject to change and then you build a stable interface around them so that even as so that as they evolve They don't interact with everything else while they're changing.
Speaker 1: Well, new code, when you're still working on it, when it's still in flux, when you don't really know what it's going to look like, that's the perfect example of code that is likely to change. By focusing on locality of behavior, by keeping everything together, you make sure that any changes don't escape out of that file and or out of that module. If it looks like you're repeating yourself, you're violating dry, but then you extract the code somewhere and you reuse it when you realize that it needs to change, that there's something you hadn't accounted for, when you've got multiple changes to fix. To fix now, multiple places to fix, you've violated protected variations. On the other hand, if you can just defer extracting that bit of code slightly, focusing on locality behavior, you let allow the code to stabilize.
Speaker 1: This means less churn, but it also buys you more time to find the deeper patterns, let them emerge in your code. Quickly refacturing to dry code is all very well and good. But it can be a premature optimization. It can lead your code getting kind of stuck in a local optima. Buying the space for those patterns to emerge is for me one of the greatest payoffs of thinking about locality of behaviour I think it's really interesting to think about how far we can or should push locality of behavior when building our apps. I think that's a discussion. I think it's case by case, app by app, team by team. But from where I'm standing, it's super exciting. Okay. So leaning into Django or um API maybe, bootstrapping in A
Speaker 1: application circa twenty twenty-four. Django has a grain. It wants you to do things a certain way. And the tip here is: well, go with it, right? So I just want to quickly run you through through some of the tools that I've been using, how I've been thinking about using Django in the last year and a half. So we start off we 've got a model and a view and I've been using Neapolitan's CRUD View. To quote Will Vincent, 90% of every app is basic RUD operations. So last year I introduced Neapolitan, which is my take on quick rudviews for Django. I have to say I couldn't be happier. Apart from the odd template view, Neapolitan has totally replaced my use of the generic class-based views from Django. So let me show it to you
Speaker 1: I have a model, just a simple bookmark model there. It's got a URL, a title, and a little note, and a Boolean field for whether it's a um a favorite or not. I want to get it on screen. So in my URLs. pile here I import Neapolitan's CrudView and I just define it. I say what model it's going to use. I say what fields I want it to show and I say I want to be able to filter on whether it's a favorite or not Then I just add it to my URL patterns there and that's it. It's ready to go. Neapolitan's CrowdView provides the standard list, detail, create, edit, and delete views for a model, as well as the hooks you're going to need to be able to customize any part of that. Neapolitan provides base templates and reusable templates tags to get get to make getting your model on the page as easy as possible. Where you take your app after that is up to you, but Neapolitan
Speaker 1: will get you started. The key point for me is that your code lives in a single class. It's all right in front of you. As we said, maybe you get to the point where you break it out into a separate view. But in general, you're going to go a long way before you need to do that. In the meantime, you've got a working space. You've got the working space that you want to allow your code to develop in a kind of controlled environment. CrudView itself is just a single class. It pulls all the familiar API from APIs from Django generic class-based views. Get form, get query stack, get context data, and so on. All those methods that you're used to, but it's got them into one place, in one file. With Neapolitan, I'm never asking myself, oh, where's that implemented? Oh, where's that overridden? I don't need a separate website just to
Speaker 1: look up where my method is defined. Right? It makes a big difference. I'm having massive fun with it and I recommend it to you. Do go and check it out. Then the second bit Is you know we've got a model and we've got views, but we're going to send HTML using Jang um using Django templates. In a way I don't have too much to say about this thing, right? Just send HTML. Like what what more do you want? But that's the whole payoff of moving away from API first development. You're not creating serializers and then building your HTML in a whole separate stack, but you're doing it there and then in your Django. After a decade of not really pushing to limp the the limits of templates, I've hardly been using them for a decade, right? I'm rediscovering them What's built in is great, and I've over the last year I've lost count of the number of times I've gone to the docs searching for something, found it right there, and just been able to use it.
Speaker 1: But then writing your own template tags is easy and powerful if perhaps a little underdocumented. On top of that though, the ecosystem is really exciting at the moment. So what's been mentioned this week, Django Unicorn, Django Components, Slippers, Django Cotton, they've all been called out Adam Hill of Django Unicorn famous just put out DJ Angles or Djangles, which is another take on this comp custom components in Django's idea. Joss Thomas and Adam were plotting on Mastodon last to bring la Mastodon last week to bring us component islands. Well I don't even know what that means, but it sounds really exciting. So who knows where this going? The point is it's a really vibrant time. And as I stand here now, I can't who would have imagined back in 2018 we'd be so excited about Django templates
Speaker 1: And then the other big tool of course is HTMX and template partials. This was the change. Pushing HTML HTMX it gets us a long way. It's worth a Looking at all the extensions. I'll just say that now. On the HTMX website, there's this long list of extensions, extra things you can do. I don't know, you want to put up a toast, one of these uh alert toasts. It's got a remove extension that will take it out after a few seconds. You don't have to build that yourself, it's just Free. So do check those out. I'll just quickly introduce HTMX because we've been it's been mentioned so many times this week. You get a button, it's got these attributes, a You know, when you what's going to happen there, you all know you click the the button, it it will replace whatever comes back in in the in the template. Um the point is that everything's there and it's all the All in one file. On top of that, I added Django template partials.
Speaker 1: And this was an idea again from the HTMX website. You've got a full template and you only want to render a small part of it when you get back. So you've got a bookmark list and you just want to get a single bookmark, the bit for a single bookmark back where you can wrap that in a partial and pull that back. You can do that with Um Django templates anyway, you can put that into an include tag, but it kind of does two things. One that's a bit heavyweight, and two it breaks your flow because you've suddenly got the detail bit for the bookmark in a totally separate file. So again, it's this idea of locality of behavior. Let me just quickly show you to you if you haven't seen it. First of all, you you you load the partials tag and then you use a partial death to create some reusable bit That you're going to want to use. And then later on, somewhere else, you can use that.
Speaker 1: So you call it by name there. And you can just go, look, I want to put it put my great reusable bit in there. So I just go partial great bit and it appears. That's the worst example. I should improve it. Um With that in place, you can just change the template name in your view and uh you know here I'm just going like okay that's the bit and you can add the hashtag at the end to the great bit and the template loader will know knows to go and fill find the template, looks to see if it can find the template like normal. If it finds the template it will look to see if that partial is defined and if the partial isn't defined it will raise a template not found like you used to. The point is that it's as transparent as your view layers can be, you're still using the standard flow, request response flow, and you're returning a basic template response like you always have.
Speaker 1: So for me, the combination of HTMX and template partials has been really powerful. It effectively lets me stay in the one template file with one view class supporting that for much longer than it was possible to do before. This is a real productivity boost. You can simply get more done. You can tidy it up. And again, you can tidy it up later once the structure becomes apparent. And then so finally I just wanted to mention um Alpinejs that Karen again was talking about yesterday. It's lightweight. It only weighs a few kilobytes. So if you're just doing one thing, like one simple widget once, well just use ValillaJS. But if you're gonna use Alpine or if you're gonna do this all over your site, those few kilobytes buy you an awful let. You get state management, reactivity, you know, all sorts of great things. You get good bang for your buck.
Speaker 1: It's easy to use, it's just a simple script tag. And it's another one of these locality behavior things. So it looks kind of like this. Again, you uh even if you don't know this, we define a a component with uh a account there. And we we output the count in the paragraph tag there with the X tag tag and on the click event we'll just increment the the count. It does exactly what you think it does, and it's all just there in front of you. For me, I think it's it's the closest I've been to jQuery in a long time. There was this bit, I don't know, long time ago. It was beautiful, just a bit of jQuery. And that all went away. And Alpine brings that back. Anyway. So I just want to come back then to forms. Some you know how does that for me forms are the missing link.
Speaker 1: Right. They they they play this this dual role between it being a data sanitation layer and then presenting the UI. And you can make it pretty and you can do all those things, but As soon as you start doing J J JavaScript again, you're worried, oh no, am I gonna have to start writing serializers? Am I gonna have to but duplicate everything I've done in my form logic? No, you're not JavaScript has got a form data object. So instead of sending JSON back, you can without with Alpine. js you can configure a form data object. And then you can dispatch an event from your Alpine component and have something listen to it, which uses HTMX to dispatch the request to the browser, which then gives back Alpine. js enhanced HTML, which you then inject
Speaker 1: back into the into your document with with the HTMX swap mechanisms. Doing that, you never have to do this sort of double step into JavaScript land or into JSON land and then writing back HTML. There are some tricks. You you you know you there's some lifestyle bits. You need to call um next tick with um Alpine to make sure that the DOM is settled before you try and update update the the Alpine. You need to call maybe HTML, HTMX Prepare, or you need to call Alpine Init Tree sometimes to get the life cycle. But those are really workable and they're much simpler than having to reconstruct the HTML in your template. So for me it's gone a really long way. That combination has got has enabled us to get what I think uh of as a genuinely viable product, a G VP.
Speaker 1: The next year is going to tell what you know whether we manage to get from this bootstrapped GVP position to fully fully sustainable, but the combination of HTMX, Alpine, Tailwind, Django with Neapolitan, I really recommend it to you. Looking forward, I just want to wrap up with by saying that three things um talk about three things that were on our roadmap. Um The first is async and collaborative editing. Everything we've built so far is done on sync code only. That's as it should be. Sync code is much more, is much simpler, order magnitude simpler than async code, and it's easier to maintain. Most of our app is edited only by one person, but then we get they do a review meeting where the first thing they do is all open up the laptops, all open up the same project, all at the same time, and we need
Speaker 1: Things to be synced. I don't have too much to say of that. I'm gonna go home and study Ken's talk in very a lot of detail, and then I'll have more to say on that. Um The thing I would like to do is upstream some of that back to the Django admin. It's one of the longest standing complaints about the admin that it doesn't handle multi-user environ the multi-user environment. Um, you know, you've got a big list editable table. You're busy editing away, and someone comes in underneath you and makes an edit and you click save and then your work's all lost. The second one is performance and latency. Pards is hosted in AWS's Paris region, but are user bases distributed everywhere from Kenya to Vietnam to Bangladesh to China and beyond. Such performance is key. Most users are office-based. We don't really have mobile phones and poor network
Speaker 1: connections to worry about. Something's on the radar, we'd like to get out of the office, but it's not a pressing need now. But the latency to China is still significant. We've done the basics. I don't think we've pushed even half the buttons yet. Most of our operations are reads, so heavy caching on the ORM and template rendering will go an awful long way. Maybe we needed some edge processing. I don't know, a little outpost in Nairobi or Shanghai. I don't know. The point is that's at the high level. The latency problem is precisely where we were all meant to move meant to move to think of to rich clients and SPAs in the first place. Supposedly smaller package sizes from shipping JSON to the client rather than HTML would lead to better performance With the modern web, we've seen that that isn't the case. Bundle sizes are so big that pages remain
Speaker 1: unresponsive for an age. It's actually slower for the browser to convert JSON backs to HTML in the client than it is just to accept HTML in the first place So I I have to say I don't know where we're going to end up with this performance and latency issue, but it's quite exciting. What I can say is that whatever we do will be based on user needs. It will be based on evidence and we will grow into it. We're not going to just default to doing anything simply because that was what Facebook felt like they needed a decade ago. The final thing I want to mention then is um integrations because of course we're gonna need an API for for integrations. Folks want to plug their data into Power BI or whatever it is that they're using. But this the shape of this is really different from a REST API powering your application.
Speaker 1: What folks want is a read only endpoint for a CSV on a subset of their data. They don't want like the full What not. And with Neapolitan's Crudview, you've already got auth and you've already got filtering and all the rest. So you just add a little extra handler onto the onto the CrudView and that gives you your CSV that they need for that thing. Right? It's it's not the same beast at all as what we were talking about. So let me just put up this one to finish. I stepped down from the fellow in order from the fellow role in order to get back to building things with Django rather than just working on it. I'll say again, I'm having the time of my life. In 2018, I never expected Webb to swim back swing back Django's direction. The thought then was, oh, well, should we merge DRF into court, which I know Jeff's got. Thoughts on. I think that moment's gone.
Speaker 1: Maybe in 2018 we should have done it, but we didn't. I think that moment's gone. There are too many exciting things happening around APIs now for us to take what's probably a dated solution and crown it, right? But the point is that's kind of moot because by l I hope you've seen that if we lean into Django, it's not even a question that you need to think about until you get well, well, well out of the gate. So an API? Well, maybe.
Speaker 2: Thank you, Carlton. Times have most certainly changed. We've got a few minutes for questions. Any out there?
Speaker 1: Come on, Danielle, come on, let's have it.
Speaker 3: Being a little bit conservative, we've avoid you know, we could have ended up with um cryptocurrency mining and chat GPT built into the core of Django if we had s a slightly different attitude. So
Speaker 1: we have time in a week longer we might.
Speaker 3: So um we've skipped w we we've saved ourselves from some um uh missteps by doing that. But the arc of history is really quite long, and even ten years is not that long. I I wonder if you're willing to look Even further into the future than you've looked into the past and and suggest where this might be going
Speaker 1: Well the bottom line is we can't know, right? The future is unknowable. But here's the I've been thinking about this since you asked related question in Vigo about this. Is I think the problem we have to solve now is what to To paraphrase someone much clever meat, we would call kernel jango plus batteries Thus installable batteries. So packaging isn't the issue it needed it once was. So why why can't we do everything in in plugins, in ex in third-party packages, in things that we can pip install? Pip is easy and reliable and whatnot But we can't maintain the massive battery pack of every possible option we want. And we can't experiment in Django either. So how But the trouble is if we just have kernel Django, there's this whole ecosystem, which is our superpower.
Speaker 1: It's one of Django's absolute best resources, is the packaging environment, but nobody knows what to pip install So somewhere between batteries included, it's all in Django, but we can't maintain it, and a kernel Django with a mystery land. There must be a happy story where we can recomm recommend to people 20, 30, 40 packages which are reliable. cover most cases and give them guidance into the deeper ecosystem. Then I don't think we have to try and be crystal ball predictors because we can experiment in You know, Simon was talking about a plug-in-based or a third-party pack at way without risk to the core kernel jangle. That would be my kind of thought. I don't know how we do that, but that's where I'm leaning to.
Speaker 2: Okay. Well, thank you very much, Carlton.
Locality of behavior keeps the code needed to understand a feature together in one place, making it easier to reason about, maintain, and iterate on. It is especially useful while code is changing because it delays premature abstractions and lets better patterns emerge.
Discussed at 4:15Neapolitan’s `CrudView` can provide list, detail, create, edit, and delete views from a model, with the fields and filters declared in one class and added to the URL configuration. It also supplies base templates, template tags, and customization hooks.
Discussed at 10:30A template partial lets a view return only a named fragment of a larger Django template, while retaining the normal request-response and template-rendering flow. Combined with HTMX, this allows interactive page updates while keeping the view and related markup together for longer.
Discussed at 14:24Use the browser’s `FormData` object from Alpine.js, dispatch an event that HTMX handles, and return HTML from Django for HTMX to swap back into the document. This avoids an extra JSON-and-client-rendering layer, with a few lifecycle calls such as Alpine’s `nextTick` sometimes needed.
Discussed at 17:25Synchronous code is much simpler and easier to maintain, so it is the appropriate starting point for an application whose requirements do not yet demand asynchronous behavior. The talk’s application was built synchronously first, with collaborative editing and async work left for a later roadmap stage.
Discussed at 18:57For integrations, the application may only need a read-only endpoint that exports a filtered subset of data as CSV for tools such as Power BI. This is much narrower than a REST API intended to power the whole application, and it can be added as a handler to a Neapolitan `CrudView`.
Discussed at 22:05Not necessarily. By using Django templates, HTMX, Alpine.js, Tailwind, and tools such as Neapolitan, a small team can build a viable product without an API serving as the application’s primary interface; an API can be added later for specific integration needs.
Discussed at 22:51Note: 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 July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026