rmonvfer3 days ago
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.
miki1232113 days ago
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.
rmonvfer3 days ago
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.
whilenot-dev2 days ago
> 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.
ilumanty3 days ago
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.
stackskipton3 days ago
Tiangolo is type who wants to do it his way without a ton of input . One of reasons Litestar was developed.
[deleted]3 days agocollapsed
arthurcolle3 days ago
Iam-abbas has a good FastAPI boilerplate
whilenot-dev2 days ago
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:
app
├── controllers
│ ├── task.py
│ └── user.py
├── models
│ ├── task.py
│ └── user.py
├── repositories
│ ├── task.py
│ └── user.py
└── schemas
├── extras
│ ├── current_user.py
│ ├── health.py
│ └── token.py
├── requests
│ ├── tasks.py
│ └── users.py
└── responses
├── tasks.py
└── users.py
A structure around mini apps always turns out to be more beneficial for keeping boundaries intact in the long run: apps
├── tasks
│ ├── controller.py
│ ├── models.py
│ ├── repository.py
│ ├── schemas.py
│ └── service.py
└── users
├── controller.py
├── models.py
├── repository.py
├── schemas.py
└── service.py
[0]: https://github.com/iam-abbas/FastAPI-Production-Boilerplatehynek2 days ago
The fancy word for that is Vertical Slice Architecture btw and it's the only way for complex apps that doesn't end in chaos.
shakna2 days ago
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.
kreelman3 days ago
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.
no_carrier3 days ago
> 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.
canadiantim3 days ago
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?
croemer3 days ago
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.
canadiantim3 days ago
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
ubercore2 days ago
For forms and auth style apps, Django will probably always be the better choice.
rmonvfer3 days ago
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...
sureglymop3 days ago
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!
dontlaugh2 days ago
They seem to fill the same purpose as django's signals.
hnuser1234563 days ago
It's a python web framework, for those curious to know more before clicking through.
LigmaBaulls3 days ago
thanks, saved me some time.
davepeck3 days ago
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.
murkt3 days ago
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.
intalentive3 days ago
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.
wraptile3 days ago
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.
icedchai3 days ago
It definitely seems worth checking out. I've been using FastAPI for a few years now.
hariwb3 days ago
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.
rtpg3 days ago
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.
[deleted]2 days agocollapsed
rmonvfer3 days ago
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.
rtpg3 days ago
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.
gwking2 days ago
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.
twothreeone2 days ago
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.. >.<
zokier3 days ago
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
holler3 days ago
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.
jaza3 days ago
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.
ddejohn3 days ago
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.
NeutralForest3 days ago
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.
sambaumann3 days ago
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.
devjab3 days ago
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.
SalmoShalazar3 days ago
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.
devjab3 days ago
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.
WD-423 days ago
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.
bootsmann2 days ago
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.
jessekv3 days ago
I usually just use asyncpg.
MitPitt3 days ago
You can use asyncpg in SQLAlchemy
jessekv3 days ago
Yep! But I don't.
jg0r33 days ago
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.
globular-toast2 days ago
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.
8organicbits3 days ago
> 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.
brokegrammer2 days ago
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.
globular-toast2 days ago
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.
adrianh2 days ago
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?
globular-toast2 days ago
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.
mrweasel2 days ago
> 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.
jnpnj2 days ago
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 ?
Ralfp2 days ago
Tell me how to extend Django's default JOIN clause with custom AND, eg:
SELECT \* FROM t1 LEFT JOIN t2 ON t1.id = t2.key AND t2.used_id = 213
dvdkon2 days ago
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.
JodieBenitez2 days ago
> 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.
cbzbc3 days ago
How do people deploy this framework typically - speaking for myself, I found NGINX Unit somewhat fiddly.
brokegrammer2 days ago
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`.
intalentive3 days ago
uvicorn systemd service behind nginx reverse proxy
indigodaddy3 days ago
Or just docker/uvicorn
devjab3 days ago
With uvicorn.
cr125rider3 days ago
Litestar is awesome. It’s great it’s got more than a single maintainer too.
androiddrew3 days ago
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.
andrewstuart3 days ago
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.
hangonhn3 days ago
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.
rick12903 days ago
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?
brokegrammer2 days ago
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.
scriptr2 days ago
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
whinvik2 days ago
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.
adr1an2 days ago
Ready, set... jump! There's a reference application [0]
mleonhard2 days ago
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...
talos_2 days ago
Just want to comment that Litestar is awesome. Docs are great and the built-in caching feature is very convenient!
thelastbender123 days ago
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.
dtkav3 days ago
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).
punnerud3 days ago
Good to see it using port 8000 as default, and not Flasks 5000 (does not work on Mac anymore)
femtozer3 days ago
I wasted a couple of hours recently before realizing that AirPlay was using port 5000...
baggiponte3 days ago
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”
Copenjin3 days ago
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).
thewisenerd3 days ago
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.
monadoid3 days ago
This is well written, thanks!