Marrying Django and FastAPI đź’Ť

This video features Joseph Victor Zammit at Django Day Copenhagen 2024 in Copenhagen, Denmark.

Marrying Django and FastAPI đź’Ť
0:32:17
Published October 13, 2024
335 views

Django Day Copenhagen 2024 talk descriptions: https://2024.djangoday.dk/

Summary

FastAPI is useful when input/output is the bottleneck: unlike synchronous code, async tasks can make progress while waiting on files, networks, or other I/O, though it does not solve CPU-bound work, where task queues and multiple processes or hosts are more appropriate. Joseph Victor Zammit describes combining FastAPI with an existing Django application rather than replacing Django: FastAPI handles new async endpoints while synchronous Django and Django REST framework continue to provide the admin, models, migrations, and established business logic. He recommends isolating Django ORM access in repositories, converting Django models into Pydantic domain models, and organizing business logic, services, and routes by domain. This structure keeps the migration low-risk, makes testing and future service splits easier, and supports choosing tools based on a team’s experience rather than treating Django and FastAPI as mutually exclusive.

Key takeaways

  • Async improves throughput when applications spend time waiting on I/O, but it offers little benefit for CPU-bound work.
  • A Django and FastAPI application can run as two processes in one service and share a database and codebase.
  • Keeping Django models, migrations, admin, and DRF can make a gradual migration less risky than replacing Django outright.
  • Repositories can isolate Django ORM access, while Pydantic domain models and services keep the rest of the application framework-independent.
  • Domain-oriented organization narrows the code needed to understand a feature and makes later restructuring easier.
  • The choice between Django and FastAPI should reflect the problem and the team’s experience, not a presumed competition between the frameworks.

Summarised automatically from the transcript.

Transcript

4,645 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:07

Speaker 1: We're ready for the very first talk of the day. This is Joseph Samit. Also, not the very first talk at uh Gengo Day Copenhagen. uh but someone who has been very consistent in delivering fantastic talks so we're very glad that you're back.

0:28

Speaker 2: Thanks.

0:28

Speaker 1: Um and very, very exciting topic that you have for us. Let's give a big big hand for Joseph.

0:40

Speaker 2: Good morning everyone. I am happy to be here with you for another Django the Copenhagen event, and this time I'll talk to you about marrying Django and Fast API. So, quick show of hands. Um, who here has heard about fast API? All right, for for those for those that didn't raise their hands, um it's the fastest growing web framework. Um Uh this is the JetBrains developer survey numbers. So in 2021 it was 14%. In 2023 it's 25 probably by now it's uh It's doubled the number from 2021. There is this nice little website called Star History, which shows the

1:26

Speaker 2: graphs with uh uh GitHub rebo uh stars. And uh this shows that uh FastAPI is very well lagged alongside Django and Flask by by its users, but it's probably going to surpass both of them. So my question to you is now instead of just hearing who has actually worked with it. All right. And uh Let's see what what do you like about it? Why did you work on it where you would did you work with it instead of Django or Flask, for example? Lightweight didn't need a CMS. Lightweight didn't need a CMS. More reasons. Type hints. Type hints, yes, uh, more reasons.

2:12

Speaker 2: Simplicity, yes, that everyone has his his own opinion on it. In my experience, I had uh I worked on it um for a client, so I didn't really choose and the reason was async or native async uh by async IO so I would like to I would like to clarify um uh how async why async is important in our Python web applications. So here there's this simple uh script snippet that loops from one to three and passes them to the say hello function. Time. sleep here suspends execution for the amount of seconds that we want and then prints the time.

2:58

Speaker 2: So if we start at the zero second mark, it takes one second to print the first, another two seconds to print the third one, and another three seconds. To uh to print the third one. So total execution six seconds. Um here in the comment time. sleep is simulating an I. O. blocking call. Why IO blocking? Because A process can be limited uh the speed at which it progresses, it can be limited by CPU, by memory, by access to cache. And when it's I. O. bound, we mean that the rate at which the process progresses is limited by input-output. So for example, if you're reading data from a file, you're limited by the file system. If you're making an

3:44

Speaker 2: API request over the network, you're limited by the network. So that's what we mean by I. O. blocking. So let's look at async now. Uh sorry, this is the execution of the sync process over time. So this is the first one to print the first one two seconds, one second, two seconds here, and two seconds. Seconds to print the third one. So look, let's look at an async snippet. This uses async. io library, which gives us the async a8 syntax, and here we use the Python 312 task group to run the tasks passing one, two, and three. So let's look at the output of the async. io snippet. As we can see here, instead of taking a total of three seconds, they appear like they are started all at the zero second mark.

4:34

Speaker 2: In fact, this is when they are printed. So, but they are not running concurrently. If we compare them, it's the same an async is the same thread doing all the work. But in up uh in contrast to synchronous in async um an IO blocking oper operation does not block the progress from doing the work. So that's why they appear like they have started off from the zero second mark because these are not blocked this if the subsequent tasks after the first one are not blocked by the by the first IO blocking task. So, when should we use uh async IO? When is it important? When does it make a difference?

5:20

Speaker 2: And the answer is when IO is the bottleneck. We'll uh soon get back to this. I hope that is clarified. And We soon get back to I. O. bottlenecks. For the past year and a half I've been working for a company called Deep Desk. It's about enhancing the lives of customer support agents by providing a bunch of features as written in as I pasted in this uh LinkedIn post here. Um and to do that there's a whole architecture. It's deployed we deploy the system In a multi-tenant architecture, so every client has their own set of services and architecture kind of.

6:06

Speaker 2: So for the purposes of this talk, I simplified that architecture Into a few microservices here. So these let's start from the front end and UI components are here. They talk to two services. One of them is the account config. Which we call admin because uh the admin interface by Django is one major feature of that. And the project features backend with which in to which the the pro the the front end and the user interface component stock to actually use the product. Then each of these talks to a bunch of microservices. These services we can treat them as a black box for the purposes of this talk, and they are mostly deployed in Flask and Fast

6:52

Speaker 2: API. This product features backend was deployed in Flask and the account config for the Django admin was deployed in Django and DRF synchronous. So we have one process here, one process here, and they also shared libraries each with their own repo, um, admin and backend So from now on I'll refer to them as admin and backend to simplify the talk. So we had this requirement where Customers wanted to upload files to their configuration, to their account configuration, and hundreds of files. So of course we wanted to take advantage of a sync or remove the bottleneck of uh

7:37

Speaker 2: input output and to that fr to to move from this here To something that can handle async was the shortest part was to use Django async And we did. We started using async. It this was around last December, so it was kind of just coming out. And It's one thing to start an agreement project using a Django Async. But we had a large code base. We had our own middleware, custom middleware. We use third-party libraries. And uh The experience to migrate those was not straightforward, uh even because I wasn't personally experienced writing Python

8:22

Speaker 2: async code, but um It was it was it was a bit hard and also we couldn't work with DRF in an async manner, so those views were still kind of lost because DRF is not async and is not planned to work async anyway. Um my personal experience I felt a bit like this sprinkling Async await syntax and also the sync to async and async to sync adapter functions around the existing Middleware and also writing tests for those. So that felt a bit time-consuming and kind of wasn't that promising going forward Now

9:07

Speaker 2: I should acknowledge that the effort to make Django, which is a huge framework, where casing is a huge, it 's a huge effort and it shouldn't go unappreciated. But this is my personal experience and maybe it's biased from the fact that I've been writing synchronous Python and Django applications for around 15 years. So maybe that was one of the reasons. So with that said, so now we have this structure. At the same time, uh that this was happening, and We saw the effort in rewriting those. Uh there are people experienced with fast API on the team. And so the argument was there were other issues that related more to reworking a microservice. these two microservices into one

9:54

Speaker 2: because this features backend made requests over this co account to the admin and we weren't reaping the benefits of separation of const concerns. So while this these two can in theory scale together, we weren't uh it can in theory scale separately, we weren't scaling them separately. They uh we expected the demand on the service to be on the back end to be much higher than the admin but it wasn't a big deal so the the decision was made to join these two into one and initially the idea was to have fast API and remove Django completely. But uh at the same time this was happening, I noticed a talk at PyCon US, and it was about using the Django

10:39

Speaker 2: ORM from a fast API application. The talk was my by Mia Bayic. There's a link to her profile and to her talk because she later then delivered it also at DjangoCon Europe. The same talk. about you uh using the Django RM from fast API. And to us, uh at least to me, I'm referred to as the Django guy by the way at at at at at work because I'm always seeking the good part of Django and it wasn't just the good parts, it was that we had to rework a lot. We had all the d data models defined uh in Django. We were making heavy use of of the admin. We had a lot of code in DRF with some business logic and there um so it wasn't just uh that straightforward

11:24

Speaker 2: it would it would be uh a big effort anyway to to migrate to Fast API completely. So I proposed we look at this and uh proposed a suggestion. Where is we join these two services and have we remove Django async process and we have two processes on one host, one is Fast API. Exposing the backend and the other will still be sync, Django, and Django Rest framework. They will have the same database. The shared libraries that we maintain And have to be maintained to work for both services can be reworked into this now monolithic uh service. And uh also we reap the benefits of keeping

12:10

Speaker 2: the Django models defined. in Django, what are these benefits? Off the top of my head, migrations, uh, how do you track the evolution of it? It's an excellent tool to track the evolution of of of the your database models. The Django Admin, you simply define a model and you have a basic CRUD that you can very easily extend and customize. When writing tests you have mature uh factory libraries like uh factor boy and model bakery. So those are not to be underrated when when doing this kind of decision. So we had this. So we we worked towards this. This also was the path of least least resistance because We just created a alongside the other Django

12:57

Speaker 2: apps we just created a backend folder and put a main dot pie to host the fast API instance in it. So this is it. This is our wedding ceremony for the marriage. So or all it took for this this code by the way there's a prototype A proof of concept repository, there's it's linked uh in the talk. So it's on GitHub and you can check it out, run tests, run it, whatever. So this is it. So we have the fast API instance to which you attach roots, you attach configuration here. We're attaching the middleware that this is the only place where we use sync to async adapter function. And there's a dpconnect function, it's synchronous and it connects the default database.

13:44

Speaker 2: Now this is Django internals. I'm not sure this is the best way to do it, but it works. Um we awaited since it's converted to async and then uh we let fast API process the rest of the request. So um so that's our marriage. So is that it? Can they live happily happily ever after now? Let's ask uh So with that said, this is as as an experienced Django developer, this is a post by Benjamin here, and I think he's been dealing with Working with Fast API after probably a decade and a half of Django. And I'll take you, I'll give you a few seconds to read it.

14:32

Speaker 2: I relate a lot to the things he expressed in this post. It's actually one of the reasons for this talk. So I assume read it you read it by now. As I said, I relate to a lot in there. Maybe I I have come around on the typing, on the typings part, because typing I feel celebrating. I have come around the type on the typing because uh I think by having type annotations An entire class of errors, an entire category of errors is moved from runtime to type checking time. Also, I feel it makes the code more readable once I got used to it.

15:20

Speaker 2: And also makes refactoring easier and also results in improved at air support like autocomplete. So the rest I fully relate to. So let's see what how we married FastAPI. and uh Django as one of the points I I asked ChatGPT what makes a good marriage and uh among the reasons ChatGPT said number four is de factors maybe for a good marriage, our compromise and flexibility. Lot so let's see the compromises we made in our structure. So initially when we started with the main dot pie, we could expose a fast API root or view in Django and it has to talk to the Django ORM. We to talk to the ORM you just import

16:05

Speaker 2: it and execu and run statements, ideally using the async ORM feature. Django provides. So, how do we go about implementing this box here? And for that, we used concepts from the domain-driven design. So domain-driven design has been around since 2003. There's a book by Eric Evans. There's a link to there. There's not a link, there's there's the name of the book in the talk notes. Um and domain-driven design is about uh Prioritizing the rich. Anyway, you can read the description. I think you are I think everyone here should uh who is familiar with domain-driven design.

16:52

Speaker 2: Okay. Um so it's an alternative way to structure your applications. It's an architectural approach that gives you patterns to to structure your code. Why? And this is definition is taken from a work in progress book that is written is being written by Don Wages, who has been uh a long time uh she's been active for a long time in the Django and Python communities. She's writing this book called Domain Riving Django. And why one of the reasons also there's a good talk by David Sedden at PyCon uh PyCon UK 2023, which is about domain-driven Python. domain driven design and Python.

17:37

Speaker 2: I think Django and DRF give us that they they break the complexity of a webplicate of a web application by giving us uh by breaking it down into Smaller, more manageable components, giving us engineers a language to talk about uh what we are going to do. For example, if we get a ticket. We talk about how we're going to do it in terms of models, um middleware, views, serializers in the case of Django REST framework. Domain-driven design is about abstracting further away from what the framework provides. And since we're marrying two frameworks, I think this is ideal. So this is from the proof of concept repository. And here we have a project which is a Django project.

18:23

Speaker 2: Here we have the agents up. So far it's just Django. The admin, the models. py, and we have a conversation model which has one or many messages. Now the objective is to expose an async route to handle a webhook that updates the data in here. Conversation messages and makes an IO-bound request to a bl to a micros to external microservice. So for that we will use a fast API route. So we have the structure, the root and the ORM. These are already in place. That's what we get when we start it. How do we structure this? And you we used domain-driven design concepts. So we have repositories here, which uh a repository in domain-driven design.

19:09

Speaker 2: uh abstracts uh the persistent layer uh the persistence layer away from uh the data access. Services encapsulate business logic. So if we have a handle webhook business ser business process it we will have a service for that and Python tech models are different from the models as as we have them in Django um because in Django we have data models. In mod models, I I have written Pydenting they don't need to be pythonic by DDD, by domain driven design. But Pytenting is the is the de facto I think data validation library in Python now um and so we define our models using pydentic and they reflect business models

19:54

Speaker 2: or business uh in one of the the objectives of domain driven design is ubiquitous language which means we use the same uh terms and uh vocabulary as used in uh for example tickets as written by product owner or even the user interface. So we use this structure. Repositories here return models which are not Django model instances but instead the Pythonic models we have here and one constraint or one compromise that this embodies is that we talk to the Django RM only from the repository So, this is how the structure looks like. So, this is the Django project, and we created a back-end directory.

20:40

Speaker 2: Which hosts the fast API. Then we have a list of domains. In the case of the proof of concept repository, it's the domain is called messages because we are dealing with messages And then it has uh models, repositories, services, and routes. And dependencies is something that we use for plugging things together. Um the advantages here are the flexibility this this gives us. So the flexibility. If I need to split these models, I just create a subdirectory and uh add more models there. If this needs to be merged into some other domain, we just merge it. There are no uh framework artifacts, no framework components, it's all our code. So

21:25

Speaker 2: we have total fritten there. If we want to split it again, it's easy so it's very very maintainable over time. Other advantage that I that I that I experience using this structure and that domain-driven design advertisers is the bounded scope when you look at code. So the monolith is much bigger than this. one one domain it's it's a lot of things but when you're solving this is a a a key advantage when you're solving uh delivering a feature from a user point of view or even a product point of view You just need to look at one domain. So I need to so I just look at these files and I have everything in there. And I go ahead and understand that and uh work in that direction. So I think the flexibility gained by the compromises we made.

22:13

Speaker 2: um is is really nice. So yeah, so I spoke about compromises. Let's see what compromises we made again I mentioned that in the at repository level we it's the only place we talk to the Django ORM. The ORM in Django provides async function functions which makes our code very readable because we don't need to use any adapters in our code. Here we we do this conversion. So we have these converter functions in DDD. I think the name of the pattern is factory. You could have classes that do this, but We just use a function here that converts from the ORM conversation, which is the Django model

22:59

Speaker 2: instance, to the object, to the class we use in the in the domain models, to a domain model. And that way uh the a the ORM is dealt with only from at repository level. This means that we can write synchronous tests even though the async functions even though the repository exposes async functions. So you do that using async. io. ran. And the point here is that we hit a wall as in Benjamin's post earlier when we try to use factories asynchronously. Um so we limited tests at uh at uh at repository level to be synchronous. These are the models that I

23:45

Speaker 2: mentioned earlier, the domain models. They are implemented using Pythentic validation library. And Det necked the rest of the applikation is it är en normal fast API applikation. Så som one who koms från Fast API at route level at service level they would see the jula async kod dependens in injektion. features that uh fast api uh gives us um and these are the tests they are async as well at root level so Uh we use we use fixtures uh which are in j we override dependencies using fast API tooling. And so

24:30

Speaker 2: The way we write tests is synchronous for repositories going to the ORM and uh async for the rest of the Fast API application. So this exercise taught me that it's not about technology, and I hope I demonstrate it to you that Choosing uh uh between Fast API and Django is is is is a false choice. They provide more opportunities than threat than threats to each other. This I was born and raised on an island in the Mediterranean, and this is my favorite beach. My parents used to take me there when I was a child, and my friends hate it

25:15

Speaker 2: because It doesn't have sand, so it doesn't meet the definition of beach for some of them, and it's also a hassle to get to. So it's not as accessible. But to me, the fact that it's not as accessible as an advantage because I like uh I it 's it's it's rarely crowded and uh also I I'm not a sand sand kind of I don't like sandy beaches that that much. So I I even I even know when to go there or not depending on the wind. So whenever I think about going to the beach, I I don't even make decision of which beach to go to, I just Go here. And I think we do it a lot about the set of tools we're experienced with. So it's not about tech, it's about the familiarity and experience of the team with the technology. We used Fast API in contrast to, for example, Django

26:02

Speaker 2: Ninja, which occupies a similar role in the whole ecosystem because we had team members who were experienced with fast API. I was experienced with Django, where they invested a lot in Django, where they also invested in Fast API. So marrying together Making some compromises resulted in the flexibility to have something maintainable to work onwards. That's it Thank you.

26:37

Speaker 1: Oh, thank you. So we have a a a good deal of time for questions. Uh and uh I'm gonna I'm gonna ask uh a question straight away. So with FastAPI and also Async Django we see more uh more async entering. It's becoming more accessible Um and I guess you can ask the question, is it uh does does that mean that um that it's easier to do some of the things that we used to do with task cues? Um or is it on the on the other hand should we keep using test cues and try to avoid some of the async? How how would you see that?

27:19

Speaker 2: Thank you very much. Good question. So as I mentioned earlier I explained uh why to use async and I uh I uh the conclusion was that you I async is uh is great for when IO input output is the bottleneck. So async works in the same process, but if the bottleneck is CPU, you don't gain much. Async doesn't give you a lot. However, if if you want to uh uh that's where tasks use come in because with task use you can uh distribute computation over more than one process or over more than one host if you if you you want to so that's with with async you cannot do that you async is is gives you the benefits you reap the benefit of async when the bottleneck is IO

28:09

Speaker 2: um Does that clarify? Yeah it it it certainly does. Uh thanks.

28:23

Speaker 1: Yes. Audience, do you have any questions? One in the back. Uh do we have a microphone for the audience?

28:45

Speaker 3: Thank you. I imagine another way you could have done it would be to reproduce the ORM in like SQL Alchemy or whatever you whatever fast API would usually use. Uh and I guess one reason not to do that would have been that you have to rewrite things twice and keep them in sync. But is that the reason you didn't do that that way? Uh or like is there some tool that'll automatically convert my Django models to fast API?

29:10

Speaker 2: I I we didn't look into that because this as I said it was the path of the least resistance. We kind of tried it. saw that it works and continued building with because we didn't need to um we didn't we we kind of solved it that way and uh SQL alchemy probably there is some tools to migrate. But yeah, as you mentioned, there would probably need some downtime. Like this we had zero dynamic because nothing is changing. We are just migrating I APIs from one service to another. So from a user point of view nothing has changed It's just the underlying architecture. SQL Alchemy is a different tool, I guess, compared to ORM. We could compare them, but we didn't get to that point. We try to

29:55

Speaker 2: use the pot of least resistance. Thanks for the question.

30:03

Speaker 1: We have time for more questions. Yeah, and I I was the one who had uh this post on social media. I'm I'm okay now, but uh um Uh another question uh from me then uh Joseph, uh noticing that there is a lot of work with paid models and um Django Ninja offers a kind of translation layer between the Django models and the Pythantic models. Did you enter that territory or are you happily building those two worlds separately?

30:44

Speaker 2: Um we we are not building them separately they are part of the same repository now so So but we didn't get to this we we didn't invest any time into Django Ninja because we were invested in Django and Fast API and so we just used those. didn't uh so the option was either to go with one or the other. So yeah, we had a lot of experience with Fast API and the team, so we just went there way

31:14

Speaker 1: yeah does the internet have any questions don't be shy internet um Otherwise, thank you so much for uh presenting a very exciting topic. Thank you for also all your work on uh on the prototype, I suggest we all go and try it out and uh and gain some more experience with this exciting new territory. Thank you, Joseph.

31:40

Speaker 2: Thank you

Questions this talk answers

When should I use async in a Python web application?

Async is most useful when input/output is the bottleneck, such as network requests or file access. It does not provide much benefit when the work is CPU-bound.

Discussed at 5:20

How can I combine Django and FastAPI in the same application?

Run FastAPI and the existing synchronous Django/DRF application as two processes on the same host, sharing the database and common code. FastAPI handles the async backend routes while Django continues to provide the admin and existing functionality.

Discussed at 11:24

Why keep Django when adding FastAPI instead of migrating everything to FastAPI?

Keeping Django preserves the existing models, migrations, admin interface, business logic, and mature testing tools. A complete migration would require rewriting a large codebase, including Django REST Framework code.

Discussed at 12:10

How should I structure a Django and FastAPI integration?

The talk uses domain-driven design: repositories isolate the Django ORM, services contain business logic, and Pydantic models represent domain data. FastAPI routes and dependencies use these layers, while the ORM is accessed only through repositories.

Discussed at 18:23

How do I test a Django and FastAPI application that uses async code?

Repository code that accesses the Django ORM is tested synchronously, while the rest of the FastAPI application—including routes—is tested asynchronously. FastAPI dependencies are overridden with test fixtures at the route level.

Discussed at 23:45

Is choosing between Django and FastAPI an either-or decision?

No—the speaker presents them as complementary options that can be combined. The best choice depends on the team’s familiarity and experience, and combining both can produce a maintainable architecture with some compromises.

Discussed at 24:30

Should I use async code or task queues for background work?

Use async when I/O is the bottleneck and the work can benefit from progress within one process. Use task queues when computation needs to be distributed across multiple processes or hosts, especially for CPU-bound work.

Discussed at 27:19

Do I need to replace the Django ORM with SQLAlchemy when adding FastAPI?

No. The speaker’s team kept the Django ORM because it was the least disruptive path: their models and data stayed unchanged, and they could move APIs without downtime or duplicating the model layer.

Discussed at 29:10

Note: 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.

More videos by Joseph Victor Zammit

More videos from Django Day Copenhagen