Back to Django episodes

Django · Episode 2

Modern Django: Background Tasks, Async Reality, and the Return of Boring Web Apps

A natural founder-style conversation about where Django nowadays: Django 5.2 LTS, Django 6.0 tasks, async tradeoffs, HTMX-style simplicity, admin workflows, and why boring architecture is becoming a serious advantage again.

HostRafał K.Lead Software Engineer - Backend, Cloud and Data Platforms

GuestMaya Rahman — Senior Django Engineer — Northstar Labs

Modern Django: Background Tasks, Async Reality, and the Return of Boring Web Apps

#2: Modern Django: Background Tasks, Async Reality, and the Return of Boring Web Apps

Original editorial from Softaims, published in a podcast-style layout—details, show notes, timestamps, and transcript—so the guidance is easy to scan and reference. The host is a developer from our verified network with experience in this stack; the full text is reviewed and edited for accuracy and clarity before it goes live.

Details

The episode explains why Django still matters in today's world without pretending every project should use it.

The conversation focuses on practical Django decisions: LTS upgrades, background tasks, async boundaries, admin workflows, testing, and deployment.

Django 6.0’s built-in Tasks framework is discussed as a shared language for background work, not a complete replacement for workers like Celery.

The episode contrasts modern Django with frontend-heavy stacks and explains where HTMX-style server-rendered apps can be a better fit.

The tone is natural, practical, and builder-focused: no hype, no framework worship, just clear tradeoffs.

Show notes

  • Django 5.2 as a long-term support release
  • Django 6.0 and the new Tasks framework
  • Why background jobs need clearer boundaries
  • What async Django is good for and where teams overuse it
  • The return of server-rendered interfaces
  • Django admin as an internal product surface
  • HTMX-style workflows and progressive enhancement
  • Postgres-first architecture before extra services
  • When Celery, Redis, queues, and workers still matter
  • How to modernize a Django app without rewriting it
  • Testing migrations, permissions, and background workflows
  • Advice for teams choosing Django today

Timestamps

  • 0:00Cold open: Django is not dead, but the old story is
  • 2:30What modern Django means today
  • 5:00Django 5.2 LTS and why stable releases matter
  • 8:00Django 6.0 Tasks: what changed
  • 11:00Tasks are not magic workers
  • 14:00Async Django: useful, but not a personality
  • 17:00Where teams misuse async
  • 20:00HTMX, server rendering, and simpler products
  • 23:00The Django admin as an operations product
  • 26:00Postgres-first thinking
  • 29:00Background jobs, email, imports, and reports
  • 32:00Testing the boring parts
  • 35:00Security updates and upgrade discipline
  • 38:00Why teams come back to Django
  • 41:00When Django is the wrong choice
  • 44:00Modernizing an older Django project
  • 47:00The stack Maya would choose today
  • 50:00Advice for junior Django developers
  • 53:00Closing: boring software is not lazy software
  • 55:00End

Transcript

[0:00]Rafał: Welcome back to Builder Notes. Today we are talking about Django, but not in the tired way. Not the old argument where one person says Django is dead and another person lists famous companies that use it. That conversation is boring because both sides miss the real question. The question is not whether Django is old. It is. The question is whether Django still gives modern teams a practical way to build reliable web products without turning every feature into a distributed systems problem.

[1:00]Rafał: Our guest is Maya Rahman, a senior Django engineer who has worked on internal tools, SaaS platforms, analytics dashboards, and large content systems. She has seen Django used well, used lazily, and used in places where it probably should not have been used at all. Today we are going to talk about modern Django nowadays: Django 5.2 as an LTS release, Django 6.0’s new Tasks framework, async Django, HTMX-style server rendering, admin workflows, Postgres-first architecture, and the strange return of boring web apps.

[2:00]Rafał: Maya, thanks for joining.

[2:06]Maya Rahman: Thanks for having me. I like the phrase 'boring web apps' because people hear boring and think lazy. But boring can mean proven. It can mean observable. It can mean the team understands what happens when something breaks at two in the morning. A lot of modern Django is about that: not refusing new ideas, but being careful about which new ideas are actually earning their place.

[2:30]Rafał: Let us define the phrase first. When you say modern Django, what do you mean?

[2:40]Maya Rahman: Modern Django means using Django as a full product platform, not just as a quick CRUD generator. It means strong models, migrations, admin workflows, permissions, forms, templates where they make sense, APIs where they make sense, background tasks where they belong, and careful deployment. It also means knowing where Django has changed. The framework is not frozen in 2015. It has async support, better typing conversations around it, active security releases, and now a first-party way to describe background tasks.

[3:45]Rafał: So it is not nostalgia.

[3:48]Maya Rahman: No. Nostalgia is dangerous. If someone chooses Django only because they learned it first, that is weak reasoning. But if they choose it because their product needs authentication, admin operations, permissions, relational data, content workflows, email, forms, and a team that can move safely, that is not nostalgia. That is a sober technical choice.

[4:35]Rafał: The internet often treats framework choices like identity. You are a Django person, a FastAPI person, a Rails person, a Next.js person. But real teams usually have messier requirements.

[4:50]Maya Rahman: Exactly. Framework identity is not architecture. A good team asks: what are we building, who maintains it, how often does the data change, where are the risks, and what happens when the first engineer leaves? Django is very strong when the hard part is not raw request speed but product correctness, workflow clarity, and long-term maintenance.

[5:00]Rafał: Django 5.2 became an LTS release. For non-framework-watchers, why does that matter?

[5:10]Maya Rahman: LTS means long-term support. For businesses, that matters more than flashy features. If you are running a serious application, you need a release line that receives security updates for years. Django 5.2 gave many teams a stable landing point. It is the kind of release that lets engineering managers say, 'Okay, we can upgrade, stabilize, and not chase every minor version immediately.'

[6:05]Rafał: That sounds boring again.

[6:08]Maya Rahman: It is boring in the best way. Most companies do not fail because they missed one trendy feature. They fail because they cannot upgrade, cannot patch, cannot explain their dependencies, and cannot find the person who understands the old queue worker. LTS releases are part of operational hygiene.

[6:55]Rafał: What should teams do when an LTS release arrives?

[7:00]Maya Rahman: First, read the release notes instead of guessing. Second, run your test suite against the new version early, even if you are not upgrading that week. Third, check your third-party packages. Fourth, treat deprecation warnings as future production work, not console noise. And finally, make upgrades routine. If upgrading Django feels like a once-every-four-years emergency, your process is the problem.

[8:00]Rafał: The headline topic for a lot of people is Django 6.0 and the Tasks framework. What changed?

[8:10]Maya Rahman: Django 6.0 introduced a first-party Tasks framework. The important thing is to describe it accurately. It gives Django a standard way to define and enqueue background work. It creates shared concepts around task definition, validation, queuing, and results. But it does not magically remove the need for worker processes. It is not a complete Celery replacement by itself. It is more like Django saying: background work is important enough that the framework should have a common language for it.

[9:20]Rafał: That distinction matters because people hear 'built-in tasks' and assume they can delete their queue.

[9:28]Maya Rahman: Yes, and that would be a mistake. A task definition is not the same as operational execution. You still need something to run the work. You still need to think about retries, idempotency, monitoring, failure handling, concurrency, timeouts, and deployment. The Tasks framework is valuable because it standardizes the boundary. It does not erase the boundary.

[10:20]Rafał: What is a good example of a task?

[10:24]Maya Rahman: Sending a confirmation email after signup. Processing an uploaded CSV. Generating a monthly report. Calling a slow external API. Rebuilding a search index. Anything that does not need to block the HTTP response is a candidate. But the trick is to design it so running it twice does not destroy your data.

[11:00]Rafał: Let us stay on that. You said tasks are not magic workers. What should teams understand before adopting them?

[11:10]Maya Rahman: They should understand the lifecycle. A user clicks a button. The web request validates intent and stores state. A task is queued. A worker eventually picks it up. The task performs work and records the result. The user interface shows progress, success, or failure. Every part of that chain can break. Modern Django makes the chain easier to describe, but the engineering responsibility is still yours.

[12:20]Rafał: What mistakes do teams make with background work?

[12:25]Maya Rahman: They treat background work as a place to hide complexity. If the code is messy in the request, moving it to a task does not make it clean. They also forget idempotency. If the same invoice email task runs twice, will the customer get two emails? If an import task partially succeeds, can it resume safely? If the worker dies halfway through, do you know what state you are in? Those questions matter more than the syntax.

[13:25]Rafał: So the product question is not just 'can we run this later?' It is 'can we explain what happened later?'

[13:33]Maya Rahman: Exactly. Background jobs need receipts. Task ID, status, input summary, started time, finished time, error message, retry count. If support cannot answer what happened, the feature is not done.

[14:00]Rafał: Let us move to async. Django has async capabilities, but there is still confusion. What is async Django actually good for?

[14:10]Maya Rahman: Async is good when your request spends a lot of time waiting on I/O: network calls, streaming, certain real-time interactions, long-lived connections, or workloads where concurrency is mostly waiting. It can be useful with ASGI deployments and careful code. But async is not magic speed. If your bottleneck is bad database queries, async will not save you. If your team does not understand the boundary between sync and async code, it can make things harder.

[15:18]Rafał: Why does async become overused?

[15:22]Maya Rahman: Because it sounds modern. People want to say their stack is async. But architecture should start from the workload. A simple admin-heavy SaaS with relational data and normal request-response pages may not need much async at all. A dashboard that calls five external services per request might benefit. A chat or notification system may benefit. The point is not to avoid async. The point is to use it where the shape of the work demands it.

[16:25]Rafał: What is your rule of thumb?

[16:29]Maya Rahman: Start sync unless you can name the waiting problem. If you can say, 'This request waits on three external APIs and we can safely overlap that work,' then async may be useful. If the argument is just, 'Async is faster,' stop and measure.

[17:00]Rafał: Where do teams misuse async in Django specifically?

[17:08]Maya Rahman: They mix sync and async casually. They call blocking libraries from async views. They forget that the database layer and third-party packages may not behave the way they assume. They create code that looks modern but is harder to debug. The worst version is when a team adds async to avoid fixing slow queries, missing indexes, or poor caching.

[18:10]Rafał: So performance still starts with boring things.

[18:14]Maya Rahman: Yes. Indexes, query counts, select_related, prefetch_related, caching, pagination, file sizes, database locks, connection pooling, and realistic load testing. These are not glamorous, but they are where many Django apps actually get faster.

[19:00]Rafał: What about real-time features?

[19:04]Maya Rahman: Real-time features can be a good reason to reach for ASGI, WebSockets, or Django Channels. But again, ask what real-time means. Does the user need live collaboration? Or do they just need a status page that refreshes every few seconds while a report generates? Those are different problems. Do not build a live socket system when polling is enough.

[20:00]Rafał: Another trend around Django is the return of server-rendered interfaces, often with HTMX or similar tools. Why is that happening?

[20:10]Maya Rahman: Because many products do not need a heavy frontend application. They need forms, tables, filters, modals, partial updates, and fast feedback. Server-rendered Django plus small interactive enhancements can produce a very good user experience with less code. HTMX became popular because it lets teams keep more logic on the server while still making pages feel responsive.

[21:15]Rafał: What is the wrong way to use it?

[21:18]Maya Rahman: The wrong way is to recreate a chaotic frontend, just with different tools. If every button triggers a mysterious partial response and nobody understands the state, you have not simplified anything. The right way is to use server-rendered fragments for clear interactions: inline editing, filtering a table, submitting a form, opening a detail panel, updating a status. Keep it boring and legible.

[22:15]Rafał: Where would you still choose a separate frontend?

[22:20]Maya Rahman: If the product is highly interactive, offline-capable, canvas-heavy, collaborative, mobile-app-like, or has a frontend team already moving quickly in React or another framework, a separate frontend can make sense. The mistake is assuming every SaaS dashboard needs that by default.

[23:00]Rafał: You have strong opinions about Django admin. Some developers treat it as a temporary scaffold. You treat it as an internal product surface. Explain.

[23:12]Maya Rahman: The admin is often where real operations happen first. Support teams review accounts, finance teams check invoices, editors manage content, moderators handle flags. If the admin is sloppy, your internal users suffer. A good Django admin is not just auto-generated tables. It has filters, search, permissions, clear labels, safe actions, read-only fields where needed, and links to related objects. It should help the team make fewer mistakes.

[24:25]Rafał: What is an example of improving the admin as a product?

[24:30]Maya Rahman: Suppose support reviews user-submitted organizations. A weak admin shows raw fields. A better admin shows verification status, domain, last activity, risk flags, related users, recent changes, and a safe approve button. It also prevents dangerous edits from people who should not make them. That is product design. It just happens to be internal.

[25:35]Rafał: And this connects back to Django’s strength: internal workflows.

[25:40]Maya Rahman: Exactly. Django shines when the public feature and the internal workflow share the same data model. The admin is not glamorous, but it shortens the distance between the application and the people operating it.

[26:00]Rafał: Let us talk about Postgres-first thinking. Why do Django teams often get value from staying close to Postgres?

[26:10]Maya Rahman: Because Postgres is very capable. Before adding a search service, analytics store, queue-like workaround, or cache-heavy architecture, ask what Postgres can already do well. Constraints, indexes, JSON fields when appropriate, full-text search, transactions, materialized views in some cases, and strong relational modeling. A lot of young systems add services because it feels advanced, not because the database failed.

[27:20]Rafał: What is the danger of adding services too early?

[27:25]Maya Rahman: You create operational debt before product-market clarity. Now you need to deploy, monitor, secure, back up, and debug another moving part. If the team is small, that can slow everything down. Sometimes a second service is necessary. But it should be pulled in by evidence, not pushed in by architecture fashion.

[28:20]Rafał: So the mature choice can be fewer tools.

[28:24]Maya Rahman: Often, yes. Mature engineering is not the longest diagram. It is the diagram your team can operate under stress.

[29:00]Rafał: Back to background jobs. Give me a practical example of designing one well.

[29:08]Maya Rahman: Take a CSV import. A bad design uploads the file, starts processing in the request, times out, and leaves partial rows. A better design stores the upload, creates an import record, queues a task, processes rows in batches, records successes and failures, and gives the user a results page. If the task fails, the user can see where. If it retries, duplicate rows are avoided. That is the difference between a feature that demos well and a feature that survives real users.

[30:20]Rafał: What about email?

[30:23]Maya Rahman: Email is classic background work. But even there, teams need care. Do not just fire and forget. Store enough information to know what email was intended, whether it was sent, and whether it failed. For sensitive emails, be very careful with retries. You need to know if sending twice is harmless or harmful.

[31:15]Rafał: And reports?

[31:18]Maya Rahman: Reports are another good case. A user requests a report, the app creates a report object, a task generates it, and the user gets a notification when it is ready. The important product detail is status. Pending, running, failed, completed. Users tolerate waiting much better than they tolerate uncertainty.

[32:00]Rafał: You often say teams should test the boring parts. What are the boring parts?

[32:08]Maya Rahman: Permissions, migrations, forms, background workflows, admin actions, duplicate constraints, and failure states. Teams love testing happy-path API responses and forget the places where businesses actually get hurt. Can the wrong user see the invoice? Can a staff member approve something twice? Does the migration lock a large table too long? Does the task retry safely? Those are boring until they break.

[33:20]Rafał: How should a Django team think about migrations?

[33:24]Maya Rahman: Migrations are production events. Treat them that way. Small migrations are better than heroic ones. Test them against realistic data sizes. Be careful with defaults, backfills, and locks. Separate schema changes from data migrations when needed. If the table is important, plan the migration like an operational change, not a code formatting update.

[34:30]Rafał: That is the kind of detail that never appears in a framework hype thread.

[34:35]Maya Rahman: Because hype threads are written before the incident. Good Django engineering is written after enough incidents to know what hurts.

[35:00]Rafał: Security updates are another boring but serious topic. How should teams handle them?

[35:08]Maya Rahman: They should have a clear owner and a normal process. Subscribe to security announcements. Know which Django versions you run. Keep dependencies visible. Patch promptly. Run tests. Deploy. Record it. The worst process is panic-driven patching where nobody knows what version production is on.

[36:10]Rafał: Does using an LTS release remove urgency?

[36:14]Maya Rahman: No. It gives you a supported line, not an excuse to ignore updates. Security support is only useful if you actually apply the security releases.

[37:00]Rafał: What about third-party Django packages?

[37:05]Maya Rahman: Be selective. Django’s ecosystem is a strength, but every dependency is a maintenance relationship. Check activity, compatibility, issue history, and whether you can live without it. Sometimes a package saves months. Sometimes twenty lines of local code are safer than adopting an abandoned abstraction.

[38:00]Rafał: You have said some teams are coming back to Django after trying more fragmented stacks. Why?

[38:09]Maya Rahman: Because fragmentation has a cost. A separate frontend, separate API service, separate auth provider, separate admin tool, separate background worker, separate deployment story, and separate type system can be powerful. It can also be exhausting. Some teams realize their product did not need that much separation. They want one coherent place to model the business and ship workflows.

[39:15]Rafał: Is that anti-JavaScript?

[39:18]Maya Rahman: No. It is anti-unnecessary-complexity. JavaScript is essential. Rich frontend frameworks are essential for many products. But not every settings page, approval queue, reporting dashboard, or content workflow needs to be a single-page app. Django gives teams permission to choose a smaller surface area when that is enough.

[40:25]Rafał: So modern Django is partly a reaction against overbuilding.

[40:30]Maya Rahman: Yes. It is not a rejection of modern tools. It is a rejection of automatic complexity.

[41:00]Rafał: When is Django the wrong choice?

[41:05]Maya Rahman: If you are building a highly real-time collaborative product where the frontend is the product, Django may still play a backend role, but it may not be the center. If your team is deeply invested in another stack and shipping well, do not switch just because Django is having a good moment. If your workload is mostly lightweight APIs with minimal relational logic, a smaller framework may be better. And if nobody on the team understands Django, that learning curve matters.

[42:20]Rafał: That is more honest than saying Django is always the answer.

[42:24]Maya Rahman: Framework advocacy becomes useless when it cannot say no. Django is strong, but it is not magic. It has conventions, and if your product fights those conventions all day, you should notice.

[43:15]Rafał: What is a sign that Django is a good fit?

[43:20]Maya Rahman: You have relational data, permissions, staff workflows, forms, reporting, content, email, and a need to ship reliable features with a small team. That is Django country.

[44:00]Rafał: Say a team has an older Django project. It works, but it feels dusty. How do they modernize without rewriting?

[44:10]Maya Rahman: Start with inventory. What Django version are you on? Which packages block upgrades? Where are the slow queries? Which views are risky? Which admin workflows cause support issues? Then upgrade in steps. Remove dead packages. Add tests around critical flows. Fix deprecation warnings. Improve settings organization. Add observability. Only after that should you talk about bigger changes like async views or frontend rewrites.

[45:30]Rafał: Why not rewrite?

[45:34]Maya Rahman: Sometimes a rewrite is necessary, but many rewrites are just avoidance. The old app contains business knowledge. The weird field names, the strange admin actions, the ugly management commands — they often encode real history. If you rewrite without understanding that history, you produce a cleaner app that forgets important scars.

[46:35]Rafał: So modernization is archaeology first.

[46:39]Maya Rahman: Exactly. Understand why the mess exists before you delete it.

[47:00]Rafał: If you were starting a serious Django product today, what stack would you choose?

[47:08]Maya Rahman: Django on the current stable line, Postgres, server-rendered templates for the first product surfaces, HTMX or small JavaScript where it improves interactions, a task system for background work, a boring deployment platform, strong logging, error tracking, and tests around permissions and money flows. I would add a separate frontend only when the product clearly needed it.

[48:15]Rafał: What about API-first?

[48:19]Maya Rahman: API-first is right when there are real API consumers: mobile apps, third-party integrations, separate frontend teams, partner access. But API-first can be wasteful when the only consumer is your own templates. In that case, you may be building ceremony before product.

[49:05]Rafał: What would you avoid?

[49:08]Maya Rahman: I would avoid premature microservices, premature async, premature frontend separation, and premature search infrastructure. Premature does not mean bad. It means the system has not earned the complexity yet.

[50:00]Rafał: Advice for junior developers learning Django nowadays?

[50:06]Maya Rahman: Learn the boring parts deeply. Models, migrations, querysets, forms, templates, permissions, transactions, admin customization, testing, and deployment. Do not skip straight to building APIs before you understand how Django thinks. Build something with real users, even if tiny. A club membership system, a job tracker, a booking tool, a moderation queue. Django makes the most sense when you feel the workflow.

[51:20]Rafał: What should they not obsess over early?

[51:24]Maya Rahman: Do not obsess over perfect architecture diagrams. Do not turn every tutorial app into a SaaS template. Do not add Celery, Redis, Docker, Kubernetes, GraphQL, and a frontend framework before the app has a reason. Learn what each tool solves, then add it when the pain is real.

[52:20]Rafał: That sounds almost anti-resume.

[52:24]Maya Rahman: Maybe, but good engineers are not tool collectors. They are problem solvers. A small Django app that handles permissions, imports, email, admin review, and failure states teaches more than a shiny stack that barely works.

[53:00]Rafał: Let us close with the big question. Why is boring software not lazy software?

[53:08]Maya Rahman: Because boring software still requires discipline. It requires careful data modeling, clear naming, strong constraints, thoughtful permissions, useful logs, tested migrations, honest release notes, and respect for the people operating the system. Lazy software hides complexity until users find it. Boring software tries to make complexity visible enough to manage.

[54:10]Rafał: That may be the best summary of modern Django I have heard. It is not about pretending the web stopped changing. It is about choosing a framework that lets a team build serious workflows with fewer unnecessary parts, while still adopting newer pieces when they solve real problems.

[54:35]Maya Rahman: Exactly. Django is not cool because it is old. It is useful because it has survived enough real-world use to know where the boring work lives.

[54:48]Rafał: Maya Rahman, thanks for joining us.

[54:52]Maya Rahman: Thanks for having me.

[55:00]Rafał: That is Builder Notes. Thanks for listening.

More Django Episodes