Can you get fast, secure and easy-to-read Python REST APIs? Yes, but not with Flask!
This article is also available on Medium.
In modern web development, we are flooded with so many tools for frontend and backend that it can be hard to pick one when starting a new project. While the battle of frontend frameworks is raging every day, another one is taking place among backend tools, too.
Python for the web
I recently talked a bit about Flask and, in particular, I showed how quick-to-setup and easy to read it is. This web microframework is very lightweight and relies a lot on modularity: there are now countless plugins (developed either by the creator, Armin Ronacher, or the community) that make it easier to achieve common tasks: user auth, connection to specific databases, WebSockets management…
In my latest article, I focused more on the HTML serving features, and Jinja templating, but of course Flask can also be used to create REST APIs.
The FastAPI project started only a couple of years ago; it is a creation of Sebastián Ramírez Montaño, aka Tiangolo. His goal was to create an optimised Python API framework that would take the best out of all the existing alternatives and combine them into an easy-to-use and high-performance lib. Although it also has a plugin system, FastAPI is supposed to provide you with most of the features you need out-of-the-box.
Note: for those more into frontend web development, it’s a bit like the difference between Angular and React. Whereas Angular has a strict structure and plenty of built-in features, making it a powerful but opinionated tool, React lets you choose what you put in your project and can lead to very different project architectures… 🙂
Over its (short) existence, the new contender has attracted the attention of the community, in particular because of its very high speed (hence the name: “FastAPI”). We’ll see later on why that is and how we know that FastAPI is really fast.
In terms of building a project from scratch, though, I think that both libs are about as quick for prototyping: you just
pip install the package and then paste the contents of the “getting started” example in a
server.py file. And both frameworks can use the well-known Jinja templating engine to automatically generate complex content from your Python data and display them in top-notch modern HTML.
But it’s worth noting that FastAPI explicitly focuses more on REST APIs and prefers separated frontends served via reverse proxies, as shown in their project generator Github examples.
Finally, both Flask and FastAPI have nice docs (even though FastAPI has more pages about how the project got to be, which I really like!) and they’re easy enough to put into production using either containerisation (with Docker) or low-level servers like Uvicorn or Gunicorn.
Why compare Flask and FastAPI?
Both those tools aim to solve the same problem overall: giving Python devs in easy and readable way of creating web servers. But they each have their own approach and they don’t exactly meet the same needs.
I’ve experienced both frameworks and I have found that, to me, for medium-to-large scale projects, FastAPI usually creates a better work environment. Why is that? How has Tiangolo “merged the best of all the alternatives”? In this article, I’ll give a list of what I believe are FastAPI’s best features and why they are interesting for us programmers.
But something that I have to point out right now is that, as usual: there is no “best tool”. The better tool for the job depends on the context; so, here, I’ll show you why I think FastAPI is better than Flask for large projects that require a certain level of maintainability, data checks and ease-of-deployment – but this does not mean that Flask should never be used! I already showed you nice examples of how quickly it can create web pages this week, and I’ll give some other examples at the end of this post where I think Flask is better suited to the situation 🙂
A quick tour of FastAPI’s “winning” features
Here is a list of some important features of FastAPI (as listed in their docs) and why I think they’re an improvement over Flask.
✅ Why data validation is crucial
To me, that is the big forte of FastAPI: it’s built-in data validation system.
You know how, when you work on an API, you expect data to come in with a precise format and to be outputted using another well-known schema? That’s all fine and well but, with basic Python, this is just about having the devs and the users comply to your rules and hoping that no one got it wrong.
Enters: Python 3.6 and the data types (via annotations).
This is arguably one of the most powerful features of “modern Python”: despite being a duck-typed language, Python 3.6+ lets you specify some additional info in your code to further specify the type of data you’re working with.
This can be useful at several levels:
- for you and your friends, as humans reading the code: it’s always nice to know what type a variable is because it directly hints at what we can do with it and what sort of values it can have. (And also, let’s be honest: we rarely change the variable type over the course of our program, so we’re not suddenly “quacking” differently… so why not write actually it down?)
- for your IDE (if it’s a somewhat up-to-date and/or filled with add-ons workspace): most of the time, IDEs help you write code with autocompletion; but those tools tend to have issues with dynamic variable types and “undecided” typing: how can you expect a computer to guess the proper variable type and show you its associated fields or methods out of the blue? Thanks to Python type annotations, you explicitly tell your IDE that the variable
xis of type “integer”, for example, and therefore the IDE will be able to bring up all the int-related info when you touch this variable.
- for automatic data validators: this is what FastAPI provides us with through its use of the Pydantic lib – it’s basically like having someone go through your data and checking that each chunk is of the right type, according to the schema you gave them beforehand… but that someone is an algorithm that will sound the alarm if it finds a mistake! Once again, for this task to be automated, you have to add some info because plain Python is to “fuzzy” to do this properly 😉
The idea with auto validators like Pydantic is that you first define the format of your data, their schema. Then, the tool compares the fields and the type of each field in the incoming/outgoing data chunk and sees if it fits the expected keys and key types from the schema.
In the case of Pydantic, the schema is defined using a simply Python class:
It is intuitive and accessible even to Python beginners, and it is powerful enough to represent all basic data types (strings, ints, floats…) but also containers (via the
typing Python3.6+ built-in module with
Dicts…); it even handles optional data slots with the
Also, you can see that Pydantic automatically converts values when it can: here, the
"123" id (passed in as a string) has been transformed into a
123 int, and the datetime has been auto-parsed into an actual Python datetime object, for example.
With this lib, data is validated automatically upon instance create so if there’s an error when instantiating the model with your data, you directly get all the relevant info:
This is safer than relying on your peers to obey your schema and it is much more robust in case of API update!
🔒 Why built-in security schemes are handy
Another interesting feature of FastAPI is that it provides us with common security schemes, namely the HTTP basic, the OAuth2 or API tokens. This allows devs to quickly setup a login/password authentication system, to add tokens in their URL parameters or their query bodies, or to build something more complex using the OAuth2 flow.
The OAuth2 flow is pretty hard to setup, so it is quite valuable that FastAPI just sums it up to a few lines of code:
This is an advantage of using this lib: it encapsulates the daunting concepts into neatly ready-made tools for you to use (while making sure that they are implementing properly!).
You can of course get fancier with JWT tokens but this code already gives a basic form of security to your app.
This is nice because, more often than not, making an app secure is not easy task. So much so that you might postpone this and run an unsecure project for way too long… By giving you dev-friendly securing schemes, FastAPI reduces the development time of this feature and spreads this habit among coders.
↔️ Why interoperability is precious
We’ve already talked about Pydantic, and we’ll talk about Starlette a little later on.
But a clever choice from FastAPI is to insure interoperability between their lib and those dependencies; in other words, if you were to take some piece of code you’d written using those other libraries, it should run seamlessly in your FastAPI app.
This insures shorter refactors and reduces the overall development time of your project. Also, if you’re onboarding new people that are not yet familiar with FastAPI but that know about either one of its dependencies, they’ll feel more comfortable and participate in the dev more quickly!
🛠 Why dependency injection can go haywire
FastAPI is also very good for managing your dependencies.
Suppose that your API is connecting to a database somewhere; frameworks like Flask require you to open the connection at the beginning and store it in a “global” variable that is then imported in all your other files to access the DB:
FastAPI has another say in this: it insures that you meet all your tool requirements at the route level. Rather than passing an object around from import to import, you define it as “dependable” (by wrapping it in a generator) and use the lib’s
Depends object to require it for a specific API route:
This is cleaner because you only use your actual object in the
deps.py script, and you can very easily share dependencies between routes. Basically, it’s an extremely powerful tool for code organisation and factorisation 😉
📚 Why auto-docs are great
Have you ever been in charge of writing up the docs for an old piece of software every one has forgotten about? I’ve had this displeasure, and trust me: it’s tedious work. Not only do you have to run from room to room to try and find the grumpy dev that was around at that time and overheard how this snippet of code was supposed to work; but you also have to make sure that it’s still working like that today, that the dependencies are okay, and you’ll have to run the thing blind for quite a while before you start to make sense of it.
All this to say: you should always document your code, and you should always keep this doc up-to-date! Write stuff down while it’s easy and obvious to you, don’t just hope you’ll remember this later on and that you’ll pass it on to your friends over lunch. In my opinion, documentation should be written down and well-organised. The point is that if you’ve done something once in your life, it should take you less time to redo-it a second time because you know exactly where to get the info to guide you in solving this problem.
Of course, this is an ideal that is hard to follow – and I honestly don’t manage to get this level of docs on every project I do. But if this is your frame of mind, you have more chances of building a long-lasting and easily maintainable project: your future colleagues and even you future self will be so grateful for that 🙂
Also: documentation doesn’t have to be hundreds of pages long with complex sentences and over-the-top scenarios. Documentation should be clear, straight to the point, and most importantly, consistent with the code in its current state.
Note: if you happen to run several major versions (or branches) of your project in parallel, like Python 2 and Python 3, then you should make sure that you have separated docs for each and that it’s clearly identified which one refers to which code version.
That’s where automatic docs are an amazing tools: by directly generating the docs for your API, FastAPI insures that what you share with your colleagues, what you give your API clients, what you call “the API”, is actually what is implemented. The docs are always up-to-date (because they’re rewritten when you publish your changes in the code) so there are no inconsistencies. Better yet: they follow well-known conventions to give you a standardised and user-friendly visualisation of your routes and their input/output schemas.
By default, FastAPI can use two famous doc libraries: either Swagger or ReDoc. Both these tools are great but I do have a preference for Swagger because it lets you test your API directly in the browser in a very intuitive way (even including the auth scheme you might have defined!).
Is FastAPI really fast?
It may sound silly but this is worth asking: does FastAPI leave up to its name? Is it really “fast”?
The answer is: yes! 🙂
Why is it fast?
The reason FastAPI manages such an increase in performance is because it is built on top of Starlette, an extremely powerful and efficient web microframework. This sublayer makes intensive use of the Python asyncio module to create a very high-speed but still lightweight base web toolkit. FastAPI comes on top of it to add the dev-friendly features we talked about previously: data validation, automatic docs, etc.
Under the hood, Starlette itself depends on another tool: Uvicorn. This ASGI server implementation (for Asynchronous Server Gateway Interface) has blazing-fast efficiency and is the base for most fast Python web frameworks nowadays. In comparison, Flask relies on a WSGI server (Werkzeug), which is the old standard and can only do synchronous Python apps. Roughly put: Flask is doomed to use synchronous programming at the lower levels of its toolchain while FastAPI as a headstart with an asynchronous server building block: Uvicorn.
How do we know it’s fast?
Speed measurements and comparison between frameworks is usually done via open benchmarks like the one from Techempower. Don’t forget that these benchmarks crunch a lot of data and that they should always be taken with a grain of salt because it is quite easy to manipulate the data into showing you what you want to prove…
Most studies of these benchmarks show that FastAPI performs very well, even compared to Go or nodejs-based frameworks; but let’s stick with our own problematic for now: how does FastAPI compare with Flask?
This time, there can be no doubt: FastAPI just performs better on all charts (here, I show the example for the “Multiple queries” task):
This tells us that Flask in its “rawest” (i.e. most lightweight, stripped-down version) barely reaches half the efficiency of FastAPI in handling multiple queries at once. We get similar results for the other tasks.
Something interesting is that, in this example, FastAPI manages to get ahead of both Starlette and Uvicorn… even though it’s actually built upon them!
But this odd behaviour is compensated overall if you look at the “composite scores” (that are basically the “average scores” of each item):
So, all in all, these benchmarks show us that:
- the lower-level tool, Uvicorn, gets the best result because it only contains the bare necessities, there is no overhead of any sort
- then, Starlette gets a bit slower because it starts to integrate stuff to make web dev easier: you can make nice requests and create in-process background tasks, it supports WebSockets, it has utilities for cookies and CORS…
- then, FastAPI comes next: it’s again adding a little bit of complexity in exchange for speed; but at that point, you have a full-fledged API microframework with lots of nice features
- finally, Flask is lower in the chart with only a third of FastAPI’s score: it’s clearly slower than the Uvicorn-Starlette-FastAPI toolchain in all measured tasks
Some moments where Flask still shines
The main strength of Flask is that it is highly customisable: this web microframework is just a skeleton ready to be filled with packages. This makes it interesting for creating very optimised environment that are specific to your project. With Flash, you get just what’s needed – no more, no less! On the other hand, FastAPI comes built-in with plenty of great features… some of which might be completely useless for your own use case (e.g. the auth schemes)!
This may be especially valuable to devs that prefer un-opinionated software: some of us believe that the tool shouldn’t impose a workflow and that you should remain free to organise your project as you want; others praise on opinionated software for its rigour, its clear goals and its quick-of-use for common problems. In that regard, FastAPI is quite opinionated because benefiting from its built-in features means always writing up pretty much the same bits of code, no matter the project. Flask projects, on the other hand, can be drastically different depending on the goal of your app and the plugins you’re using.
Also, Flask is way older than FastAPI: it was created in 2004 while the newcomer dates back only to 2019. This means that FastAPI benefits from lots of past experience, of course – partly Flask itself, by the way; but it also means that Flask’s community is significantly larger than FastAPI’s! Even if the new framework is slowly getting noticed, there are still so much more tutorials, videos, articles and discussions about Flask that you’re pretty sure to find a solution to your problem somewhere.
This large community comes in part from the fact that Flask has well-written doc and is easy to learn. It requires basic Python skills and is readable even by beginners.
All of this resulted in Flask growing into a large ecosystem. Companies and developers are used to using it. They know its ins-and-outs and it’s a common language for lots of web programmers. And oftentimes, they’ve created tailor-made tooling that is very helpful. So keep in mind that going new is good, but it might slow down things at first or even reduce your dev capabilities for a while, depending on your current coding context…
This has even inspired people to “grow” new frameworks out of Flask; and those new tools are getting traction. You might have heard of Dash, a Flask/React/Plotly.js-based framework that allows you to build interactive interfaces using only Python. Or of the Sanic framework that ranks among the best ASGI web frameworks (see the benchmarks above) and was designed to look like Flask.
When using those higher-level or cousin frameworks, it will be much easier to navigate if you are a bit familiar with Flask itself; so even if you don’t use it on its own in your projects, having some basic knowledge of Flask is a nice addition to your web dev toolbox 🙂
Once again: I’m not saying that Flask is bad and that it has no use. I strongly believe that a quick-to-setup tool like this is perfect for drafts and prototypes; and that even small-sized projects can stick with Flask.
If you aim for something bigger and if you’re concerned with security, maintainability or data validation, then FastAPI might be a better choice because it handles all of those out-of-the-box.
But FastAPI is not without flaws! In particular, if you anticipate a lot of HTML templating in your project, Django will perhaps be better suited; and Dash is quickly becoming a popular choice for interactive web-based visualisations…
Picking the right framework can be hard. As a rule of thumb, I think that you should first evaluate what the critical points of your project are (meaning the specific parts of the app that have to work properly or else everything else will crumble down) and then what trade-offs you can make safely to get sort of a “shopping list of must-have and nice-to-have features”.
What about you: do you use Python for web programming? What’s your favourite backend framework? 🙂