Thank you for writing this, I've been building a large backend with FastAPI for the last year or so and I've gone through all the levels of the purgatory.
I began using the standard "tutorial" style and started cringing when I saw the official template [1] place all CRUD operations in a single file (I've been doing Rails and Spring for a while before) and the way dependencies where managed... let's just say I wasn't feeling very comfortable.
Then came the SQLModel problems. The author pushes it very hard in the FastAPI docs (which imho are terrible because when I'm looking for docs I want that, documentation, not a fancy tutorial) but as an ORM (yes I know its a layer on top of SQLAlchemy) it doesn't even support polymorphic models and the community even has contributed PRs that have gone months without any review (is it even maintained anymore? I honestly can't tell).
I guess I'm the only one to blame for choosing FastAPI to build a big project but after having used it quite a lot (and also read its code because again, docs are extremely poor) I wouldn't recommend it for anything serious. Sure, if you want to build a quick CRUD then go ahead and use SQLModel and FastAPI, but keep in mind that its not built for complex applications (at least not without requiring you to write a framework on top, like I've unfortunately done).
So yeah, a big thank you to the author of this post because I will migrate to Litestar as soon as I wake up tomorrow.
If you want to actually figure out how to scale FastAPI for a large-ish app, including auth, testing and all that stuff, all with modern practices, "how they do it in that repo" is probably a good way to start with.
Thank you! I’m actually pretty happy with what I’ve built tbh and how far has FastAPI taken us but this repo is proof that you have to reinvent the wheel if you want to build something serious.
In any case, that’s a treasure trove right there!, I actually had no idea Polar was open source, much less that it’s built on FastAPI!
It’s such a shame that the actual documentation doesn’t even scratch the surface, I would’ve saved so much time if they just included a disclaimer along the lines of “Hey, this architecture we are showing here it’s only valid for toy projects, you will need much more work to build a real production system” but again, I guess I’m the only one to blame.
> you have to reinvent the wheel if you want to build something serious [...] guess I’m the only one to blame.
The main benefit from micro frameworks like FastAPI/Flask/Express.js is that you must build your own framework! You can pick the building blocks that will make your life easier, instead of relying on choices that made the maintainer life in full-fledged frameworks like Django/Laravel/RoR bearable. Of course, you'd need to be comfortable building frameworks and doing that work additionally to the domain modeling - pick the right tool for the job and all.
> Then came the SQLModel problems. The author pushes it very hard in the FastAPI docs
No it doesn't? The front page for FastAPI contains a pretty lengthy tutorial with no mention of SQLModel. The only time SQLModel gets a significant mention is on a page explaining connecting a relational DB, but it makes it clear that any DB at all can be used. Something has to be chosen for the tutorial, so it makes sense the author would choose their own.
If SQLModel isn't right for you then you're the only person to blame. I've been through that tutorial before and settled on plain old SQLAlchemy.
Doesn't Litestar suffer from some of this too? Do you think Litestar would be better for building complex applications than FastAPI, despite less community adoption / documentation / discussion?
Is there really less documentation? FastAPI mostly has tutorials to get started and is light on deep/reference material. A single person can only do so much.
Documentation-wise I'm mainly comparing to Django, because I agree FastAPI actually quite light on reference docs too.
I've actually tried using litestar before and always been keeping an eye on it, but for a full fledged website needing forms, auth, etc. I find it hard to move away from just slightly tweaking Django for my needs - but still I feel drawn to Litestar as it's in between FastAPI and Django but still much closer to the former. I hope/believe in time I will feel comfortable migrating to Litestar for complex sites
Looking at the docs and trying to figure out what this is for. Is it essentially when you want to break out of the "request lifecycle" and queue something to run after your response has already been returned?
It strikes me that I haven't used web frameworks a lot and never even questioned how that may not be an easy thing to do!
I think Litestar is superb for building API backends. Love it; use it; only good things to say. Their Advanced Alchemy is coming along nicely, too.
Litestar of course supports old-school server-template-rendered sites, too; it even has a plugin for HTMX requests and responses. In practice, I find that the patterns that serve API endpoints so well sometimes get in the way of old-school "validate form and redirect, or re-render with errors" endpoints. In particular, Litestar has no "true" form support of its own; validation is really intended to flag inbound schema mismatch on API calls, not flag multiple individual error fields. Using `@post("/route", exception_handlers={...})` is pretty awkward for these endpoints. I'd be excited to see some better tools/DX in-the-box here.
I've been using Litestar for over a year now, serving both JSON and templated HTML. Great all-around Python async framework that manages to be fast (faster than FastAPI), lightweight, and still has enough batteries included to host a website with auth, sessions, etc. I'm a fan of first-class msgspec support and the Controller class for nested routing.
Me too! Switched from FastAPI on a new project and never looked back. I really like how complete Litestar feels and the base will get you quite far and very reliably.
Thanks for writing this. I have similar gripes about FastAPI having developed an application over the past few years; I'm also continually surprised at how prevalent the attitude is that FastAPI has excellent docs, given how divorced the tutorial / toy examples in the docs are from real-world development and measurement of an API.
I am really disappointed at the new generation of Python frameworks' documentation, which seem to have the same "docs are tutorials + chatty blog posts which imprecisely describe the APIs" attitude of Javascript libs.
Two words: API Reference.
Have the clinical explanation of methods exposed, with actual breakdowns of what method parameters do. List them all, don't surround it by prose. List out the options! Don't make me dive into the source to find out what I can or can't pass into a parameter!
Having to play this game of "alright I want to know how to use this API, so first I need to figure out what tutorial page _might_ use this" to find the tiny examples that should just be next to the methods I care about in the reference is really frustrating.
This, I’ve already said it in another reply but FastAPI is 10x harder to use because of this. I’ve had to read FastAPI code many times to literally reverse engineer features because they are not documented in any way.
Documentation is for reference, tutorials are for learning, I just don’t even understand how maintainers don’t go crazy with the absolute lack of references…
maintainers just having to assume every behavior is needed for backwards compatibility... and you still have the absolute mess which was pydantic 1 -> 2 (or django-ninja 0.x -> 1.0)
Everyone talks about moving fast and being dynamic but everyone I know deep in this has lost like actual years to churning from this kind of behavior.
I’ll second this, and add that docstrings are becoming ever more useful as modern editors learn how to show them inline when I hover over a symbol. Starlette lacks docstrings entirely and it’s a real miss in my opinion.
Agreed 100% FastAPI works but building complex applications in it is just not great. Taking a step back (and this will date me but..) I'm still astonished how the "Python microframework world" is slowly rediscovering everything JavaEE had 15 years ago. Anyhow, this looks nice. Now tell me how to handle error cases during streaming.. >.<
I know fastapi gets the hype, but I have found plain starlette quite usable by itself. Sure, it doesn't have the whole kitchen sink, but if you just need something small and simple then it fits the bill. In comparison Litestar seems closer to fastapi/django in scope
Same, I've built all my recent api's in Starlette alone and I find it excellent. It's clean, concise, well documented, and you can extend it as needed, supporting small -> very large projects.
Nice write-up! I've heard about Litestar now and then, haven't tried it, maybe I should give it a go. I've been using FastAPI quite heavily for the past several years.
I think OP's arguments about FastAPI being hard to work with in a bigger codebase are exaggerated. Splitting up the routes into multiple files, each with its own route object, and then importing and building up a big hierarchy of route objects, isn't that hard, it does the job for me. Agreed that it's probably not well documented enough, how to structure a larger FastAPI codebase - but follow a mix of best practices and your personal tastes, break it up into modules, split it into specific files for constants / errors / routes / schemas / crud / etc, and you can scale up sanely.
I haven't used SQLAlchemy with FastAPI - for my day job I mainly connect to data stores for which it doesn't make sense - so maybe I'm biased, because I've avoided that pain.
Excellent post that actually gets into important details for real-world applications. I'm a huge fan of the design of Litestar.
> I also still think there are a lot of bad use cases for repositories and service layers that people should avoid, but that’s a digression which should probably become its own post
As a huge proponent of the repository pattern, I'll be looking forward to this post.
Pretty cool post! I'm not sure how I feel about SQLAlchemy (not the star of the post but mentioned quite a bit); it's such a big ball of state that has so many surprises, I wonder if some people build entirely without it.
There is a rather big difference between traditional SQLAlchemy and Advanced Alchemy which is also made by Litestar. We've build with pure SQL and with SQLAlchemy in the past, but since we transitioned from django ninja to Litestar, we've not used SQLAlchemy and are slowly moving away from it. Well I guess Advanced Alchemy is still SQLAlchemy under the hood.
Djangi-ninja is excellent, but it's still Django and when you aren't using a lot of the "batteries included" then you're not really using Django. I mentioned SQLAlchemy which is already "fighting Django" compared to using Django Orm as an example. We picked Litestar because it's natively async, makes it easy to use dataclasses rather than Pydantic, has really fast cold start and it has great interoperability with Advanced Alchemy.
I think Django is great, and by extension that Django-Ninja is too. Considering it runs Instagram it certainly scales as well, but unlike Instagram we aren't enough engineers to be able of ripping out more and more batteries while replacing them with our own specialized batteries.
I think the way to go with SQLAlchemy is to use the models and alembic for migrations and schema definition but to write the sql and do transaction management by hand. Losing time to figure out how a query you know how to write can be constructed within the ORM is just too much imo.
Lol same, writing SQL and directly wrangling Async connection pools always seemed way easier for me than trying to jam sqlalchemy into whatever hole I'm working with.
> So if you’re going to be writing a database-backed web application in Python, and you’re not doing Django, you are almost certainly going to be using SQLAlchemy.
I've preferred the Django ORM over SQLAlchemy, but I'm curious what others feel. I've gone so far as to use Django ORM for non-web projects as well. It takes a bit of work to extract though. If Django ORM had a better stand-alone story, I think more people would use it.
However, each User won't have the `comment_count` property. You have to manually associate them from the returned tuple.
I feel like SQLAlchemy wants to force you to do more work, whereas the Django ORM wants to provide you with the data you asked without forcing to you think about how to actually get the data from the database nor how to optimize the query. In Django, session management is done automatically, but it SQLAlchemy, you need to be aware of the session most times.
It's good to know SQL but you can use the Django ORM without knowing it. Not the same with SQLAlchemy. Could be a pro or a con depending on the situation. Definitely a pro for me because I don't like SQL.
Same here. Django's was my first ORM, and at the time I didn't get all the hate directed at ORMs (except for making inefficient queries). Having used a few more ORMs since then, I still consider Django's to have found the best balance.
I find the prevailing model of having DB rows mapped 1:1 to objects in memory and syncing changes automatically to be much more trouble than it's worth, sadly most ORMs seem to use it.
> I've preferred the Django ORM over SQLAlchemy, but I'm curious what others feel.
Same here... better API IMO, just the right amount of abstraction (ActiveRecord-like has been fine for all the projects I was involved in) and plenty of escape hatches when needed.
SQLAlchemy is just a totally different beast to Django, it has a much higher learning curve but gives you so much more power and flexibility. It's a true data mapper ORM rather than the sad Active Record pattern which starts off well and quickly gets annoying.
Obviously it will depend on what you're doing and one's tolerance for things many deem to be annoying. I've encountered people with incredibly high tolerance for slow and awkward workflows but, alas, I am not one of them.
If I had to call out one thing it would be that you can't do tests without having a database there. This results in incredibly slow tests for even the simplest things. I don't need to test database persistence every time I'm testing some domain logic. So maybe then don't do fat models and "map" the data from Django models to a domain layer? Well, congrats, you've just manually implemented a data mapper ORM, which is what SQLAlchemy is.
It works well for simple CRUD stuff, which is really useful. But it very quickly becomes a mess and a big ball of mud when you start to do more complicated things. IMO a db access layer or web framework should be completely independent of domain logic, but Django makes that really difficult.
> IMO a db access layer or web framework should be completely independent of domain logic, but Django makes that really difficult.
That is an issue/features in Django, depending on your view. You really don't get to do things the framework doesn't want. If you're trying to fight the ORM or any of the components in Django really, including the Django REST framework, you're going to lose and have a bad time.
There are certainly reason why you'd want to separate domain logic from the database access, but then Django isn't what you want. You're also going to miss out many of the things that makes Django easy to work with, like getting most CRUD operations for free.
I'm curious about your experience, I like SQLAlchemy granularity and expressiveness, but after a while I found django fits the "good enough" niche very well, it's quick and not too dirty (the name wrangling for joins is funky but it's ok). What kind of queries or logic did SQLAlchemy allowed you to write that Django would make hell to reach ?
Huge fan of Starlette too. I use it for all my projects at work. That said, for some of my coworkers, I’ve pointed them to Litestar because it’s very similar to Starlette (I think it was originally built on it) but it has dependencies injection. It’s an useful feature for some projects.
How does this compare to Django? I see you have quite a bit of Django content. How would you decide to use Litestar vs Django on a new greenfield project?
Litestar looks promising because it has most of the features a Django developer would but in a more modern package. However, since it has so many features, I hope that the project doesn't get abandoned like so many frameworks these days because unlike tiny frameworks, it would be harder to migrate off of.
I agree that FastAPI is not the best but I would not jump to another framework just because of an article.
For FastAPI I know that I can go to [1] and see what a good FastAPI code base looks like. These days even Airflow is on FastAPI but haven't looked at the codebase.
For me to jump on Litestar, I would like to see a reference codebase to learn best practices. Otherwise its one more framework whose quirks I have to get comfortable with.
Thank you for writing this - there is a very clear split you feel when using fastapi for single script web servers, vs trying to organize it. And I probably share all the mentioned annoyances around writing bigger projects with fastapi.
Connexion is also worth a look IMO. It uses spec-first development (a major benefit in larger orgs and for public APIs), and can plug into different server frameworks.
(I used to be a maintainer, but it has been years since I worked on it).
Litestar is really underrated but deserves much more usage! I’ve been meaning to try it for at least a year now, but always felt a bit scary to tell the team “hey let’s deviate from our stack”
When will people understand that those very opinionated frameworks with enticing tutorials just ruin your life in the long term?
I see someone citing Spring(yikes) elsewhere, that falls in the same category as FastAPI. You don't need Spring most of the times, a simple dependency injection library and small frameworks to handle the web routing or specific features you need are often enough (recent contenders to the throne of default app framework have the same issues).
Am I the only one who prefers to just have separate models for API and database right from the start? I know it looks not DRY, but it is. Your API and your database schema are not the same thing. It's never that long before you need them to be separate so why not do it right from the start? I feel like this might actually be a win for LLMs because you won't feel the pain of adding a new field to both the db model and API model in trivial cases.
Litestar does look great and a true web framework like Flask and Starlette. Stuff like FastAPI and SQLModel is a joke imo. Developers should be able to compose these things themselves if they want to.
Thank you for writing this, I've been building a large backend with FastAPI for the last year or so and I've gone through all the levels of the purgatory.
I began using the standard "tutorial" style and started cringing when I saw the official template [1] place all CRUD operations in a single file (I've been doing Rails and Spring for a while before) and the way dependencies where managed... let's just say I wasn't feeling very comfortable.
Then came the SQLModel problems. The author pushes it very hard in the FastAPI docs (which imho are terrible because when I'm looking for docs I want that, documentation, not a fancy tutorial) but as an ORM (yes I know its a layer on top of SQLAlchemy) it doesn't even support polymorphic models and the community even has contributed PRs that have gone months without any review (is it even maintained anymore? I honestly can't tell).
I guess I'm the only one to blame for choosing FastAPI to build a big project but after having used it quite a lot (and also read its code because again, docs are extremely poor) I wouldn't recommend it for anything serious. Sure, if you want to build a quick CRUD then go ahead and use SQLModel and FastAPI, but keep in mind that its not built for complex applications (at least not without requiring you to write a framework on top, like I've unfortunately done).
So yeah, a big thank you to the author of this post because I will migrate to Litestar as soon as I wake up tomorrow.
TBH, the FastAPI "docs" are at https://github.com/polarsource/polar/tree/main/server
If you want to actually figure out how to scale FastAPI for a large-ish app, including auth, testing and all that stuff, all with modern practices, "how they do it in that repo" is probably a good way to start with.
Thank you! I’m actually pretty happy with what I’ve built tbh and how far has FastAPI taken us but this repo is proof that you have to reinvent the wheel if you want to build something serious.
In any case, that’s a treasure trove right there!, I actually had no idea Polar was open source, much less that it’s built on FastAPI!
It’s such a shame that the actual documentation doesn’t even scratch the surface, I would’ve saved so much time if they just included a disclaimer along the lines of “Hey, this architecture we are showing here it’s only valid for toy projects, you will need much more work to build a real production system” but again, I guess I’m the only one to blame.
> you have to reinvent the wheel if you want to build something serious [...] guess I’m the only one to blame.
The main benefit from micro frameworks like FastAPI/Flask/Express.js is that you must build your own framework! You can pick the building blocks that will make your life easier, instead of relying on choices that made the maintainer life in full-fledged frameworks like Django/Laravel/RoR bearable. Of course, you'd need to be comfortable building frameworks and doing that work additionally to the domain modeling - pick the right tool for the job and all.
FastAPI used to have an emoji-ridden docs page for concurrency. Criticism was not handled well.
This made it clear to me that something about the project is off.
https://github.com/fastapi/fastapi/discussions/6656
Tiangolo is type who wants to do it his way without a ton of input . One of reasons Litestar was developed.
Iam-abbas has a good FastAPI boilerplate
If you're referring to this[0] GitHub project I'd highly disagree. I will never understand the minds of people that structure their apps like this:
A structure around mini apps always turns out to be more beneficial for keeping boundaries intact in the long run: [0]: https://github.com/iam-abbas/FastAPI-Production-BoilerplateThe fancy word for that is Vertical Slice Architecture btw and it's the only way for complex apps that doesn't end in chaos.
Bogard's example for a poor fit for VSA, in the famous blogpost, was specifically controllers.
> Sometimes these are still required by our tools (like controllers or ORM units-of-work) but we keep our cross-slice logic sharing to a minimum.
That's exactly where you shouldn't be using it! Relying on it as dogma will result in chaos.
I'm starting out with API style apps. This post was great since it covered several architectural and tool points I'd not thought of.
I think I'll use LiteStar for my app now too.
Thanks for your good comment and I 2nd your thanks to the author.
> Then came the SQLModel problems. The author pushes it very hard in the FastAPI docs
No it doesn't? The front page for FastAPI contains a pretty lengthy tutorial with no mention of SQLModel. The only time SQLModel gets a significant mention is on a page explaining connecting a relational DB, but it makes it clear that any DB at all can be used. Something has to be chosen for the tutorial, so it makes sense the author would choose their own.
If SQLModel isn't right for you then you're the only person to blame. I've been through that tutorial before and settled on plain old SQLAlchemy.
Doesn't Litestar suffer from some of this too? Do you think Litestar would be better for building complex applications than FastAPI, despite less community adoption / documentation / discussion?
Is there really less documentation? FastAPI mostly has tutorials to get started and is light on deep/reference material. A single person can only do so much.
Documentation-wise I'm mainly comparing to Django, because I agree FastAPI actually quite light on reference docs too.
I've actually tried using litestar before and always been keeping an eye on it, but for a full fledged website needing forms, auth, etc. I find it hard to move away from just slightly tweaking Django for my needs - but still I feel drawn to Litestar as it's in between FastAPI and Django but still much closer to the former. I hope/believe in time I will feel comfortable migrating to Litestar for complex sites
For forms and auth style apps, Django will probably always be the better choice.
edit: reading the litestar docs, it even has a built-in event system! I spent a couple weeks building something I could use with FastAPI...
Looking at the docs and trying to figure out what this is for. Is it essentially when you want to break out of the "request lifecycle" and queue something to run after your response has already been returned?
It strikes me that I haven't used web frameworks a lot and never even questioned how that may not be an easy thing to do!
They seem to fill the same purpose as django's signals.
It's a python web framework, for those curious to know more before clicking through.
thanks, saved me some time.
I think Litestar is superb for building API backends. Love it; use it; only good things to say. Their Advanced Alchemy is coming along nicely, too.
Litestar of course supports old-school server-template-rendered sites, too; it even has a plugin for HTMX requests and responses. In practice, I find that the patterns that serve API endpoints so well sometimes get in the way of old-school "validate form and redirect, or re-render with errors" endpoints. In particular, Litestar has no "true" form support of its own; validation is really intended to flag inbound schema mismatch on API calls, not flag multiple individual error fields. Using `@post("/route", exception_handlers={...})` is pretty awkward for these endpoints. I'd be excited to see some better tools/DX in-the-box here.
I haven’t ever used Litestar, but it seems like it would be possible to write your own decorator `@postform` that handles all of form-related stuff.
I've been using Litestar for over a year now, serving both JSON and templated HTML. Great all-around Python async framework that manages to be fast (faster than FastAPI), lightweight, and still has enough batteries included to host a website with auth, sessions, etc. I'm a fan of first-class msgspec support and the Controller class for nested routing.
Highly recommend.
Me too! Switched from FastAPI on a new project and never looked back. I really like how complete Litestar feels and the base will get you quite far and very reliably.
It definitely seems worth checking out. I've been using FastAPI for a few years now.
Thanks for writing this. I have similar gripes about FastAPI having developed an application over the past few years; I'm also continually surprised at how prevalent the attitude is that FastAPI has excellent docs, given how divorced the tutorial / toy examples in the docs are from real-world development and measurement of an API.
I am really disappointed at the new generation of Python frameworks' documentation, which seem to have the same "docs are tutorials + chatty blog posts which imprecisely describe the APIs" attitude of Javascript libs.
Two words: API Reference.
Have the clinical explanation of methods exposed, with actual breakdowns of what method parameters do. List them all, don't surround it by prose. List out the options! Don't make me dive into the source to find out what I can or can't pass into a parameter!
Having to play this game of "alright I want to know how to use this API, so first I need to figure out what tutorial page _might_ use this" to find the tiny examples that should just be next to the methods I care about in the reference is really frustrating.
This, I’ve already said it in another reply but FastAPI is 10x harder to use because of this. I’ve had to read FastAPI code many times to literally reverse engineer features because they are not documented in any way.
Documentation is for reference, tutorials are for learning, I just don’t even understand how maintainers don’t go crazy with the absolute lack of references…
And SQL Model is even worse in that regard.
maintainers just having to assume every behavior is needed for backwards compatibility... and you still have the absolute mess which was pydantic 1 -> 2 (or django-ninja 0.x -> 1.0)
Everyone talks about moving fast and being dynamic but everyone I know deep in this has lost like actual years to churning from this kind of behavior.
I’ll second this, and add that docstrings are becoming ever more useful as modern editors learn how to show them inline when I hover over a symbol. Starlette lacks docstrings entirely and it’s a real miss in my opinion.
Agreed 100% FastAPI works but building complex applications in it is just not great. Taking a step back (and this will date me but..) I'm still astonished how the "Python microframework world" is slowly rediscovering everything JavaEE had 15 years ago. Anyhow, this looks nice. Now tell me how to handle error cases during streaming.. >.<
I know fastapi gets the hype, but I have found plain starlette quite usable by itself. Sure, it doesn't have the whole kitchen sink, but if you just need something small and simple then it fits the bill. In comparison Litestar seems closer to fastapi/django in scope
Same, I've built all my recent api's in Starlette alone and I find it excellent. It's clean, concise, well documented, and you can extend it as needed, supporting small -> very large projects.
Nice write-up! I've heard about Litestar now and then, haven't tried it, maybe I should give it a go. I've been using FastAPI quite heavily for the past several years.
I think OP's arguments about FastAPI being hard to work with in a bigger codebase are exaggerated. Splitting up the routes into multiple files, each with its own route object, and then importing and building up a big hierarchy of route objects, isn't that hard, it does the job for me. Agreed that it's probably not well documented enough, how to structure a larger FastAPI codebase - but follow a mix of best practices and your personal tastes, break it up into modules, split it into specific files for constants / errors / routes / schemas / crud / etc, and you can scale up sanely.
I haven't used SQLAlchemy with FastAPI - for my day job I mainly connect to data stores for which it doesn't make sense - so maybe I'm biased, because I've avoided that pain.
Excellent post that actually gets into important details for real-world applications. I'm a huge fan of the design of Litestar.
> I also still think there are a lot of bad use cases for repositories and service layers that people should avoid, but that’s a digression which should probably become its own post
As a huge proponent of the repository pattern, I'll be looking forward to this post.
Pretty cool post! I'm not sure how I feel about SQLAlchemy (not the star of the post but mentioned quite a bit); it's such a big ball of state that has so many surprises, I wonder if some people build entirely without it.
I recently built a personal project using peewee and it doesn't have a ton of bells and whistles but it works well for what it does do.
There is a rather big difference between traditional SQLAlchemy and Advanced Alchemy which is also made by Litestar. We've build with pure SQL and with SQLAlchemy in the past, but since we transitioned from django ninja to Litestar, we've not used SQLAlchemy and are slowly moving away from it. Well I guess Advanced Alchemy is still SQLAlchemy under the hood.
Mind elaborating a bit on why you migrated away from django ninja? Just curious, I’ve been using it for some small side projects and have enjoyed it.
Djangi-ninja is excellent, but it's still Django and when you aren't using a lot of the "batteries included" then you're not really using Django. I mentioned SQLAlchemy which is already "fighting Django" compared to using Django Orm as an example. We picked Litestar because it's natively async, makes it easy to use dataclasses rather than Pydantic, has really fast cold start and it has great interoperability with Advanced Alchemy.
I think Django is great, and by extension that Django-Ninja is too. Considering it runs Instagram it certainly scales as well, but unlike Instagram we aren't enough engineers to be able of ripping out more and more batteries while replacing them with our own specialized batteries.
The most interesting thing about this project to me is that it appears to alleviate some of the warts of working with SqlAlchemy.
Pretty much every time I start a project that needs a DB I just use Django. SqlAlchemy and Alembic are usually not worth dealing with.
I think the way to go with SQLAlchemy is to use the models and alembic for migrations and schema definition but to write the sql and do transaction management by hand. Losing time to figure out how a query you know how to write can be constructed within the ORM is just too much imo.
I usually just use asyncpg.
You can use asyncpg in SQLAlchemy
Yep! But I don't.
Lol same, writing SQL and directly wrangling Async connection pools always seemed way easier for me than trying to jam sqlalchemy into whatever hole I'm working with.
> So if you’re going to be writing a database-backed web application in Python, and you’re not doing Django, you are almost certainly going to be using SQLAlchemy.
I've preferred the Django ORM over SQLAlchemy, but I'm curious what others feel. I've gone so far as to use Django ORM for non-web projects as well. It takes a bit of work to extract though. If Django ORM had a better stand-alone story, I think more people would use it.
I'm not a fan of SQLAlchemy because of the Data Mapper pattern, and abstractions that force you to still think in terms of SQL tables and expressions.
For example, in Django I can have a User object. I want a update the user's first name:
my_user.first_name = "Joe"
my_user.save()
In SQLALchemy:
my_user.first_name = "Joe"
session.add(my_user)
session.commit()
Users can leave comments, so I want a query that aggregates comments for each user. In Django:
users = User.objects.all().prefetch_related("comments").annotate(comment_count=Count("comments"))
Each user will now have a `comment_count` property that contains the number of comments they left.
In SQLAlchemy:
session.query(User, func.count(Comment.id).label("comment_count")) .outerjoin(User.comments) .group_by(User.id) .all()
However, each User won't have the `comment_count` property. You have to manually associate them from the returned tuple.
I feel like SQLAlchemy wants to force you to do more work, whereas the Django ORM wants to provide you with the data you asked without forcing to you think about how to actually get the data from the database nor how to optimize the query. In Django, session management is done automatically, but it SQLAlchemy, you need to be aware of the session most times.
It's good to know SQL but you can use the Django ORM without knowing it. Not the same with SQLAlchemy. Could be a pro or a con depending on the situation. Definitely a pro for me because I don't like SQL.
Same here. Django's was my first ORM, and at the time I didn't get all the hate directed at ORMs (except for making inefficient queries). Having used a few more ORMs since then, I still consider Django's to have found the best balance.
I find the prevailing model of having DB rows mapped 1:1 to objects in memory and syncing changes automatically to be much more trouble than it's worth, sadly most ORMs seem to use it.
> I've preferred the Django ORM over SQLAlchemy, but I'm curious what others feel.
Same here... better API IMO, just the right amount of abstraction (ActiveRecord-like has been fine for all the projects I was involved in) and plenty of escape hatches when needed.
SQLAlchemy is just a totally different beast to Django, it has a much higher learning curve but gives you so much more power and flexibility. It's a true data mapper ORM rather than the sad Active Record pattern which starts off well and quickly gets annoying.
I've been using the Django ORM for 20 years, and it has yet to get annoying. What's your definition of "quickly" — perhaps 25 years?
Obviously it will depend on what you're doing and one's tolerance for things many deem to be annoying. I've encountered people with incredibly high tolerance for slow and awkward workflows but, alas, I am not one of them.
If I had to call out one thing it would be that you can't do tests without having a database there. This results in incredibly slow tests for even the simplest things. I don't need to test database persistence every time I'm testing some domain logic. So maybe then don't do fat models and "map" the data from Django models to a domain layer? Well, congrats, you've just manually implemented a data mapper ORM, which is what SQLAlchemy is.
It works well for simple CRUD stuff, which is really useful. But it very quickly becomes a mess and a big ball of mud when you start to do more complicated things. IMO a db access layer or web framework should be completely independent of domain logic, but Django makes that really difficult.
> IMO a db access layer or web framework should be completely independent of domain logic, but Django makes that really difficult.
That is an issue/features in Django, depending on your view. You really don't get to do things the framework doesn't want. If you're trying to fight the ORM or any of the components in Django really, including the Django REST framework, you're going to lose and have a bad time.
There are certainly reason why you'd want to separate domain logic from the database access, but then Django isn't what you want. You're also going to miss out many of the things that makes Django easy to work with, like getting most CRUD operations for free.
I'm curious about your experience, I like SQLAlchemy granularity and expressiveness, but after a while I found django fits the "good enough" niche very well, it's quick and not too dirty (the name wrangling for joins is funky but it's ok). What kind of queries or logic did SQLAlchemy allowed you to write that Django would make hell to reach ?
Tell me how to extend Django's default JOIN clause with custom AND, eg:
How do people deploy this framework typically - speaking for myself, I found NGINX Unit somewhat fiddly.
I tend to deploy all Python based apps using gunicorn behind a caddy proxy. Take a look at the compose.yml https://github.com/confuzeus/simple-django/blob/master/ansib...
Deployment is as simple as `docker compose pull && docker compose up -d`.
uvicorn systemd service behind nginx reverse proxy
Or just docker/uvicorn
With uvicorn.
Litestar is awesome. It’s great it’s got more than a single maintainer too.
When I look at a litestar all it feels a lot more planned out and patterned. I wish I was better at async.
I’ve recently converted to Golang, but I’d love to come back and do a litestar app in the future.
https://github.com/litestar-org/litestar-fullstack is also nice on figuring out how to scale Litestar to a "common" app pattern that is medium+ size
I love Starlette but not a fan of FastAPI and do not use it.
I read this article but didn’t really get the sense there was anything sufficiently compelling to switch from Starlette.
Huge fan of Starlette too. I use it for all my projects at work. That said, for some of my coworkers, I’ve pointed them to Litestar because it’s very similar to Starlette (I think it was originally built on it) but it has dependencies injection. It’s an useful feature for some projects.
How does this compare to Django? I see you have quite a bit of Django content. How would you decide to use Litestar vs Django on a new greenfield project?
Litestar looks promising because it has most of the features a Django developer would but in a more modern package. However, since it has so many features, I hope that the project doesn't get abandoned like so many frameworks these days because unlike tiny frameworks, it would be harder to migrate off of.
I agree that FastAPI is not the best but I would not jump to another framework just because of an article.
For FastAPI I know that I can go to [1] and see what a good FastAPI code base looks like. These days even Airflow is on FastAPI but haven't looked at the codebase.
For me to jump on Litestar, I would like to see a reference codebase to learn best practices. Otherwise its one more framework whose quirks I have to get comfortable with.
1 - https://github.com/polarsource/polar
Ready, set... jump! There's a reference application [0]
[0] https://github.com/litestar-org/litestar-fullstack
Does it have tooling to help keep Python [0] and TypeScript [1] DTOs in sync?
[0] https://github.com/litestar-org/litestar-fullstack/blob/0996...
[1] https://github.com/litestar-org/litestar-fullstack/blob/0996...
Just want to comment that Litestar is awesome. Docs are great and the built-in caching feature is very convenient!
Thank you for writing this - there is a very clear split you feel when using fastapi for single script web servers, vs trying to organize it. And I probably share all the mentioned annoyances around writing bigger projects with fastapi.
Connexion is also worth a look IMO. It uses spec-first development (a major benefit in larger orgs and for public APIs), and can plug into different server frameworks.
(I used to be a maintainer, but it has been years since I worked on it).
Good to see it using port 8000 as default, and not Flasks 5000 (does not work on Mac anymore)
I wasted a couple of hours recently before realizing that AirPlay was using port 5000...
Litestar is really underrated but deserves much more usage! I’ve been meaning to try it for at least a year now, but always felt a bit scary to tell the team “hey let’s deviate from our stack”
When will people understand that those very opinionated frameworks with enticing tutorials just ruin your life in the long term?
I see someone citing Spring(yikes) elsewhere, that falls in the same category as FastAPI. You don't need Spring most of the times, a simple dependency injection library and small frameworks to handle the web routing or specific features you need are often enough (recent contenders to the throne of default app framework have the same issues).
love litestar.. working on migrating a couple of internal consoles to it from fastAPI.
the docs could use some love though.
i feel most of it is references [1], the "how to"s could be better.
inb4, "where pull request", i don't grok asgi or the framework nuances to be able to say how to improve on it.
[1] https://diataxis.fr/
Am I the only one who prefers to just have separate models for API and database right from the start? I know it looks not DRY, but it is. Your API and your database schema are not the same thing. It's never that long before you need them to be separate so why not do it right from the start? I feel like this might actually be a win for LLMs because you won't feel the pain of adding a new field to both the db model and API model in trivial cases.
Litestar does look great and a true web framework like Flask and Starlette. Stuff like FastAPI and SQLModel is a joke imo. Developers should be able to compose these things themselves if they want to.
This is well written, thanks!