rmonvfer 2 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.

  • miki123211 2 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.

    • rmonvfer 2 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-dev 2 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.

    • ilumanty 2 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.

      https://github.com/fastapi/fastapi/discussions/6656

      • stackskipton 2 days ago

        Tiangolo is type who wants to do it his way without a ton of input . One of reasons Litestar was developed.

    • arthurcolle 2 days ago

      Iam-abbas has a good FastAPI boilerplate

      • whilenot-dev 2 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-Boilerplate
        • hynek 2 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.

          • shakna 2 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.

  • kreelman 2 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_carrier 2 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.

  • canadiantim 2 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?

    • croemer 2 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.

      • canadiantim 2 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

        • ubercore 2 days ago

          For forms and auth style apps, Django will probably always be the better choice.

  • rmonvfer 2 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...

    • sureglymop 2 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!

      • dontlaugh 2 days ago

        They seem to fill the same purpose as django's signals.

hnuser123456 2 days ago

It's a python web framework, for those curious to know more before clicking through.

davepeck 2 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.

  • murkt 2 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.

intalentive 2 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.

  • wraptile 2 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.

  • icedchai 2 days ago

    It definitely seems worth checking out. I've been using FastAPI for a few years now.

hariwb 2 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.

  • rtpg 2 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.

    • rmonvfer 2 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.

      • rtpg 2 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.

    • gwking 2 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.

twothreeone 2 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.. >.<

zokier 2 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

  • holler 2 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.

jaza 2 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.

ddejohn 2 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.

NeutralForest 2 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.

  • sambaumann 2 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.

  • devjab 2 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.

    • SalmoShalazar 2 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.

      • devjab 2 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-42 2 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.

  • bootsmann 2 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.

  • jessekv 2 days ago

    I usually just use asyncpg.

    • MitPitt 2 days ago

      You can use asyncpg in SQLAlchemy

      • jessekv 2 days ago

        Yep! But I don't.

        • jg0r3 2 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.

8organicbits 2 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.

  • brokegrammer 2 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.

  • dvdkon 2 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.

  • JodieBenitez 2 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.

  • globular-toast 2 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.

    • adrianh 2 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-toast 2 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.

        • mrweasel 2 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.

    • jnpnj 2 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 ?

      • Ralfp 2 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
cbzbc 2 days ago

How do people deploy this framework typically - speaking for myself, I found NGINX Unit somewhat fiddly.

cr125rider 2 days ago

Litestar is awesome. It’s great it’s got more than a single maintainer too.

androiddrew 2 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.

andrewstuart 2 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.

  • hangonhn 2 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.

rick1290 2 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?

  • brokegrammer 2 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.

whinvik 2 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.

1 - https://github.com/polarsource/polar

talos_ 2 days ago

Just want to comment that Litestar is awesome. Docs are great and the built-in caching feature is very convenient!

thelastbender12 2 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.

dtkav 2 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).

punnerud 2 days ago

Good to see it using port 8000 as default, and not Flasks 5000 (does not work on Mac anymore)

  • femtozer 2 days ago

    I wasted a couple of hours recently before realizing that AirPlay was using port 5000...

baggiponte 2 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”

Copenjin 2 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).

thewisenerd 2 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.

[1] https://diataxis.fr/

globular-toast 2 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.

monadoid 2 days ago

This is well written, thanks!