Your tutorials were a huge influence on me as I began learning to code in my late 20’s and had a profound impact allowing me to start two companies and helped give me a toolset I didn’t have before. Thanks for all you do!
What do you think about the current state of Flask and Flask extensions? Are they being maintained and up to date? Or would it better to go with FastAPI and use its non-async features to have the same Flask like experience?
Something that I think not everybody realizes is that Flask does not require extensions for most things. The extensions are nice because they integrate with the Flask configuration object, and with before/after request handlers when it makes sense, but none of that is required. If an extension stops being maintained, you can always find a newer general purpose Python package that does what you need and just use it in your Flask app.
Now this is my personal opinion, but you cannot say the same about FastAPI. Even though the ecosystem of asyncio compatible libraries has grown, it is still several orders of magnitude smaller than what is available for standard non-async Python. So in my view, in terms of access to 3rd party libraries Flask is a better choice.
But if you use FastAPI and completely ignore the async aspects, you get some handy tools out of the box, Swagger/Pydantic integration being the big one. Then you're in roughly the same place as Flask where you're just using general python packages. I struggle to see any advantages to Flask vs one of Django or FastAPI at this point.
This is probably quite unpopular, seeing as the FastAPI docs have been so praised in the past... but I really dislike how FastAPI is documented, which has really put me off of the project.
The FastAPI docs read like a full project, a very dense project. Sometimes you just want to learn about a specific feature, but you walk into a page that assumes you have the context of the previous chapters, that puts me off a lot.
I also dislike how some of the documentation is absolutely enormous (the SQL chapters is one example), but I suspect this might have to do with the not-so-simple approach FastAPI takes, as opposed to Flask, which is indeed very simple.
I really can't join the crowd here. I don't think FastAPI is a replacement for Flask, even when async is taken out of the equation. Flask has some weird quirks to it (like "g"), but it really is a beautiful and simple API for the web.
But it could _really_ do with some "Reference" documentation. Sometimes I just want to know exactly what a call does or what the various parameters mean.
I agree. There are generally 4 types of documentation as per the diagram in this page[0] and FastAPI’s solely focuses on the top left quadrant and falls flat on the lower right.
I agree with you entirely about the issues with the docs, there is no API reference, and Tiangolo has rejected PRs with automated documentation before. I by no means think the project is perfect, but I think it is a better starting point for most small projects. I just don't see any advantage to Flask at this point unless you specifically want to use one of tiny handful of Flask-X packages that are actually worthwhile.
I wanted to add django but their issues aren't on github as they are using their own system. IMO an external issue tracker lead to a higher barrier to create new issues, so this isn't not necessary fair.
Also fastapi allows people to ask questions with the issues. On the 1000+ open issues, 826 are tagged as question.
It's hard to find a fair metric. Open/close ratio rewards older project with fluctuating/stable popularity. Fastapi popularity is still growing[0], so having some difficulties to work on issues is expected.
Not sure what you mean by "ignoring async". If you write your handlers as regular functions, I would imagine FastAPI is going to send them to run in an executor. So all the concurrency benefits of FastAPI go down the drain, and now your concurrency is based on threads.
As far as Swagger integration, you can use APIFairy with Flask and get similar type of auto-generated docs. Disclaimer, APIFairy is an extension that I created: https://github.com/miguelgrinberg/APIFairy
But then it would be on par with Flask, both performance wise and with regards to integration with third party libraries. That is how I'm currently using FastAPI, with regular sync SQLAlchemy for example.
The difference is that even without the benefits of async, FastAPI offers a vastly better developer experience IMHO, with its integration with Pydantic, Python type hints and the lack of reliance on global objects
I think you are oversimplifying this. If you don't know how FastAPI handles sync code, then you can't claim it is on par with Flask. I suggested it might use an executor, after looking in their code, they use a thread pool support in Starlette, which in turns imports this thread pool logic from AnyIO. I see no easy way to determine what is the size of the pool. Clearly this isn't designed as a main way to run code, it is just a quick way to run sync code w/o blocking the loop. I would not consider this up to par with the concurrency you get from Gunicorn or uWSGI using a configurable thread or process pool.
Your Flask tutorials are my webapp I Ching. Thanks a bunch. Just about to launch another Flask webapp in the next few weeks, and your tutorials are invaluable.
Oh! Before I forget - flask-migrate! Lifesaver! Thanks Miguel!
No questions, just praise: I loved this tutorial, it was hugely helpful to me when I was just getting into python and I regularly recommend it. Thanks for writing it and sharing it!
Same as most of the replies, just wanted to send my thanks for the tutorial. I used your original tutorial (before the function name change) for my dissertation platform and its been running for 6 years now.
Hi, I never finished your tut but it was a impressive to see the surface covered. I'm curious, are you still using flask ? have you tried FastAPI or Django or something else ?
This is really a matter of preference. I do not use Flask-Admin on my projects, I have always preferred to design my own admin pages than being forced to accept the choices that are imposed by these big extensions. This allows me to have a consistent UI across admin and non-admin pages.
One thing that people often complain is that it is hard and/or tedious to build tables with data, which is one of the building blocks most admin pages need. Last year I wrote a blog article where I show how I build tables in my Flask apps: https://blog.miguelgrinberg.com/post/beautiful-interactive-t....
I think that Flask has many great extensions, but in my opinion, installing flask-admin is just a tacit admission that you really wanted to use Django instead.
Hey Miguel, wanted to thank you for creating this tutorial. It started my freelancing career and I created great projects based on it. Thank you very much!
I can second that Miguel's tutorial is quite nice and sufficient to build Flask apps.
Thanks to Miguel (and also official Flask docs) I have some Flask Apps running non stop for a few years by now.
Flask is extremely easy to write in regards that the mental model is easy to follow.
Sure there is magic there but the abstractions seem "non-leaky". By contrast I can't get into Django because it seems too much magic.
The worst part of using Python for back end was deployment.
Setting up WSGI server to host Python apps was a bit of a pain.
Coming from Rails, I appreciate Django's less-magical approach. I admit it has some silly limitations though, like not being able to call methods with arguments in templates, and the whole mixin view generic classes, just to name a few
That being said, Django is great, and so is Python :)
This is how I started my journey with web development.
I was a Data Scientist and I superficially used Flask to build an internal matching tool between internal products and competitors product (after an ML model).
Then I started the Mega-Tutorial to build a small app to track our foosball matches and run a team tournament.
Eventually, I wanted to accelerate my learning and joined a bootcamp (Le Wagon) where I learned Ruby on Rails from scratch. My Flask introduction with this tutorial was really helpful to understand things on almost all layers.
I always recommend this tutorial to people who come from Python and want a sneak peak to web development. I am very grateful for Miguel's work and for sharing it.
Miguel has updated this guide (available for purchase). In my opinion, it's worth every penny as a starting point into building larger Flask apps, but does include some material that isn't optimized for production-ready Flask apps (e.g. the chapters on authentication).
I learned to use Flask with this tutorial, which complemented the already good Flask documentation. This allowed me to spin-up web interfaces to make monitors, remote control panels and so on very easily. Than you very much Miguel, it was a great gift.
This was my go to for learning Flask, I wish I never found it. Flask has a very niche use case in my opinion and if you need something to scale (in terms of code complexity) Flask is the absolute worst decision you can make.
Try Django instead. If you read my comment history you'll see that I learnt this the hard way.
In my experience it's quite the opposite. We use Flask for a large-scale webapp in production quite successfully and it's enabled us to reduce application complexity significantly.
While you get up and running faster with Django, in the long run you spend more time fighting it, whereas with Flask you only spend time on stuff that you actually need.
Yep. This is exactly how I feel. Django is great to get projects going, but eventually you just end up fighting it a lot.
I even recommend people not to install too many flask extensions, just use Django if that is how you like your development. Flask is much better when you just build for it - It takes longer to deliver, but you'll be enjoying that project for years.
Different strokes for different blokes I guess. For me, Django has always been a massive pain to get up and running with, but once the groundworks done it just gets easier.
This is inspired by Elixir's compile-time config per environment/release/etc...
Instead of a huge settings.py, I have:
settings/__init__.py : Import the correct settings
settings/core.py : The stuff that Django generates and that you touch almost never
settings/base.py : The basic settings of your project (your apps, middlewares, ...)
settings/envs/dev.py : Development settings
settings/envs/prod.py : Production settings (read from env vars, the 12 factors app way)
settings/envs/test.py : Test suite settings
2. Separate your business code from your Django apps
In most project, I have the package `myproject.lib` which contains all the business code, easily testable and mockable. Then I have the Django apps in `myproject.apps.<appname>` which imports from `myproject.lib`.
This allows you to make your views quite small because all the logic is not there.
3. Most of the time, you don't need class-based views.
A small function which call your business code and render a JSON document, or an HTML document is more than enough. Django provides many function decorators that are easier to reason about (login_required, permission_required, require_http_methods, ...).
4. You might not need an API
Server Side Rendering is coming back into trends, huge SPA that are heavy on the client's resource are becoming less frequent. Then, why would you need a REST/GraphQL API?
You can work with HTMX, Django Forms, small views that call your business code and return some small rendered template, then sprinkle some Alpine.JS (or jQuery) on top of it, and you get your SSR SPA that scales well enough.
5. Deploy with gunicorn and whitenoise
By default, Django won't serve staticfiles in production, you need to distribute them via a CDN. Which is a bit more complicated to deploy. Instead, use Whitenoise to force Django to serve staticfiles but with the correct HTTP cache headers so services like Cloudflare can take over after the first request (usually done by your E2E test suite by the way).
Then you only need one Docker image, one gunicorn, and if you deploy in Kubernetes, one Ingress resource.
6. Use django-anymail
This is, by far, the best library out there. I usually make a mailjet account, add my API key to my settings, and use the mailjet backend of django-anymail. No SMTP setup required.
All of those tips might seem obvious, but they get you far, very far. Also, IMHO the urls.py from Django is vastly superior to the route decorator from Flask.
To me its not the app settings or structure but the view/viewset/mixin and plugins interactions that are black holes. I only skimmed the docs but I don't know who does what and when. Everyday I scream "magic" .. probably more than a rails guy.
Like I said, you don't need class-based views if you split your code correctly.
You have a function, which takes a request and URL parameters, and returns an HTTP response. You add some decorators to require an authenticated user and/or some permissions. That's it.
This is no different than flask views.
Mixins are an OOP pattern that promotes spaghetti code if not done carefully, especially in Python where there is no encapsulation.
ViewSets are from the Django REST Framework, this is simply all the boilerplate code that you copy/paste hundreds of times. And even that, you might not need it if your API is different enough from your database schema. Even more if you don't need an API.
There is nothing "magical", every single framework have a happy path, you should follow it if you decide to use this framework.
And no, Flask is not a framework, it is a library, much smaller scope, much more boilerplate code that you need to copy/paste, or you build your own framework on top of it.
Flask is about the freedom of choice while Django made the decisions for you 10 years ago. Having some experience with Rails, SpringBoot, Phoenix, Symphony, Laravel, Flask, Express, etc... it turns out that I don't want to make those decisions every single time, for every single project.
Which is why I put all the logic into a single place.
Extra methods on Django Models? Nope. A function in my business logic package which takes a model instance as parameter? Yes. Models should only contain data.
Huge views that do more than one thing? Nope. A function, or a class, in my business logic package which takes the request data (POST form, GET query string) and returns the data to feed to the template and/or the "state" needed to decide which template to render? Yes. Views should be small.
Also, split your templates, don't be afraid of `{% include %}` (which works well with HTMX as well).
This methodology also have another huge advantage. If you need to walk away from Django, you can. Your business logic won't change that much (there are not many difference between a Django Model instance and an SQLAlchemy model instance, or even a MongoEngine model instance). Your business logic knows nothing of HTTP or serialization.
Django, and Flask or any other framework are just the glue between the WSGI/ASGI and your business code.
If your code is async but your web framework isn't, you can still put a `asyncio.run` or `trio.run` inside your view, and you won't have to change a thing once you switch to an async web framework.
Basically, I use Django for:
- the ORM
- the Forms
- the routing with urls.py
- the settings management
- the templating engine
- the builtin User system
- the builtin admin website
- the vast ecosystem (django-anymail, django-tailwind, django-money, django-flags, django-social-auth, ...)
What I dislike about Flask is:
- routing is tied up to the views (via a decorator)
- the request object is a global singleton instead of a function parameter
- no builtin admin website (this is just so useful to get things going at the beginning)
- there is a lot of boilerplate to make the different extensions work together where django is just about adding a string to INSTALLED_APPS
Thanks for you thorough answer, I'll see how it goes for me in the coming months. I may find myself surprisingly happy.
btw, didn't django add routing decorators too ? I always prefered it to split file because .. it's a small amount of information that needs to be tracked separately mentally if in urls.py .. plus inclusion/sub-routes feel confusing (but that may only be me)
When I'm onboarding on a huge code base, someone from the frontend reports a bug, I'm like "ok so where does this URL go?", I open the urls.py (which can be split with include), and from there I navigate to the relevant piece of code quite easily.
But with routes as decorators, you need to know the structure of the code beforehand. Which can take some time if you're onboarding a new project.
IMHO, having the route next to the code managing the request does not add useful information to the code being modified.
Also, thanks to django-flags (a feature flag django extension), I can easily enable/disable routes with feature flags in the urls.py without overwhelming the "request handler" with irrelevant information.
strongly disagree with this. flask is what you make of it. you create your design patterns. for example, here's an MVC boilerplate i use for all my flask apps: https://github.com/esteininger/flask-mvc-boilerplate
After reading your comment history and searching it, it seems to me that you never learned to use Flask. Jumping into new project and learning a framework at the same time means that you may have to restructure everything after you have learned it.
I learnt it quite well actually. Used it in a few projects one quite large.
It worked ok for internal apis not open to customers, but even then Django was much better as just setting up the admin side provided a lot of value to the business (for free).
All forgotten now but wasted a huge amount of time glueing things together that came for free with Django.
For the items covered in this tutorial, I would rather recommend Django. Better documented and arguably more suited to the task. For an API alone e.g. along with an SPA I would consider FastAPI.
Miguel is a great teacher; it's difficult to find proper (up to date!) text-based tutorials in this era. I will also give a shoutout to his recent React Mega-Tutorial, which I've been learning and using his tutorial as one of my sources for learning React.
I did his flask tutorial a year + ago before I started learning react. I'd love to see the workflow for developing a backend Flask API first (without Jinja/render_template). It seems less straight-forward than FastAPI which I am using because I think it's simpler to develop APIs first (ala the name)
The celery-Flask tutorial saved my bacon recently. It sometimes surprises me that the Ruby community rallied so hard around Rails and was able to integrate all these things like job queues (ActiveJob, Sidekiq, DelayedJob etc) right into the main web framework. "batteries included" indeed.
Whereas Flask still has more of a pick-and-choose approach.
fastapi is valuable and useful tech. But it's also very over hyped with all the it's fanatics trying to throw it at every problem. Simple admin dashboard for few dozen users? FASTAPI. Backend for a mobile app? FASTAPI.
There is more useful and battle tested solutions out there for flask than fastapi (due to it's obvious time in the market, but still).
It's also interesting to check number of github issues for both frameworks (14 vs >1k).
Async python is not inherently "faster" than sync, more scalable? maybe, easer to write parallel code? yes. But async does not magically make anything faster.
> synchronous and needs multiprocess deployment
Async python still has the issue of the GIL, you still need multiple processes to scale out with async.
The only seriously useful thing async gives you is the ability to run multiple async IO operations in parallel while processing a single request. But most async request processing is still being written in a somewhat sync way, "awaiting" each IO operation within a view/request processing function.
I tend to only use async for long running requests (web-sockets, SSE, long polling), or if I have a view that is calling lots of slow IO which doesn't depend on each other. Think views that are calling multiple REST APIs, and multiple DB requests, you can do them all at once. That is incredible rare though, usually requests do depend on each other and so have to be run sequentially.
Yes, sorry didn’t mean to suggest you didn’t. Comment was more to clarify to others as the simplistic “it’s faster” can cause people to misunderstand the nuance, which is important as the developer UX of async is worse than sync. I believe we should stick to sync unless there is a significant reason not to.
The trouble with all these things is what the definition of faster is. If it’s “requests per second” then yes these async frameworks are incredible on these benchmarks with their somewhat simplistic view code (I haven’t looked in detail at the one linked, but many often are only echoing back a response).
Really for the vast majority of developers the speed that matters is “time to first meaningful paint” - the time it takes to get a webpage to display on the users browser, or for an api the time until the response is ready to be passed by your front end code. Async doesn’t increase the speed at which that will happen, except in some situations with heavily paralyzable IO within a view.
It is however true that you can get more out of your hardware with async if your are handling 10s or 100s thousands requests per second. Very few of us ever get close to that though.
(Again not suggesting you don’t know this, commenting for others)
Fair enough. Indeed - "fast" for async programs does not come out of vacuum. We just don't need to wait for IO (db, other http calls, whatever) which is common in web applications. Thus it "feels faster".
Though personally when I mentally imagine web frameworks "speed" - it is requests per second as well as getting better bang for your buck out of your hardware.
what you don't understand is that FastAPI is not a web framework, so it is neither fast nor slow, it's just a patchwork of libraries on top of Starlette.
Thanks for this detailed comment. I think people really misunderstand async python on web servers. Async literally only helps when you are io bound otherwise it hurts
I was a diehard Flask fan for years until I finally decided to try FastApi. Wish I switched sooner. Free swaggerdocs is pretty nice, and dependencies get rid of so much code duplication.
I'm somewhat of a Flask pro but have struggled to find a good entry on how to use it as a backend for a native iOS app (for someone who completely missed the Swift train). I guess it starts with migrating to Fastapi?
Speaking of backend development, recently I gave Jooby[1] a try after discovering it was one of the world's top performer in Tech Empower's web framework benchmark[2].
Surprisingly enough, it's terribly easy to put together a REST API with Jooby. I was expecting a lot of arcane tricks to set things up, but it's one of the less.verbose frameworks out there.
Not using Flask, but I've been using Django as backend for various iOS apps over the years. My recipe is just a REST API library + Django OAuth2 Provider. I imagine if I were to use Flask, I'll do the same thing: implementing the REST API and gate it using OAuth2. Plenty of library in iOS side that make hooking up with OAuth2 relatively easy.
What is flask missing? FastAPI has swagger and open api stuff but it’s not like it’s required or necessary most of the time. I use my basic flask server as the most dead simple rest API backend for my iOS apps without any issues
As others have said: What has one to do with the other? As a Flask pro you probably know that you can create great APIs with it. And iOS apps can consume them.
Happy to see people continue to find my tutorial useful and relevant. If you have any questions, I'll be happy to answer.