Django versus Flask with Single File Applications2019-04-03
A lot of people pick Flask over Django because they believe it is simpler to start with. Indeed, the Flask front page includes an 8 line “hello world” application, while the Django default project has 172 lines in 5 files, and still doesn’t say “hello world”! (It does show you a welcome rocket and have a full admin interface though, both are pretty fun).
It is also possible to make a single file “hello world” application in Django, although it’s probably something most Django developers haven’t seen done. It takes a bit more code than Flask, but it’s still quite understandable.
Let’s take a look at the Flask and Django implementations and use them to compare the two frameworks.
We’ll be using the simple “hello world” application from the Pallets page on Flask (plus a security fix). It has one page which gets a name from the URL or the default “world”, and returns that with a “hello” message.
Here is the code, 10 lines long, tested on Flask 1.0.2 and Python 3.7.2:
html.escape to the Flask example, to fix a security problem.)
We run it with:
Here’s what’s happening:
- An application object is created with the name equal to the module’s import name, the special variable
- The decorator
@app.route('/')is applied to the function
hello(). This means that when the URL
/is visited, that function will be run to generate a response.
hello()function does two things. First it gets the value of the
nameparameter from the URL arguments, or the default value “World”. Then it returns a string which contains that value, which Flask turns into an HTTP response.
Not too much to understand!
This is a bit longer at 31 lines, but stay with me, all will be explained! Here is the code, tested on Django 2.1.7 and Python 3.7.2:
We run this with:
Here’s what’s happening:
- We configure Django’s settings.
Normally Django’s settings get defined in a separate file, but here we’re using
settings.configure()to set them directly (which has a small caveat).
- We set
DEBUGenvironment variable is set to
"1". This is not strictly necessary but it’s very useful to switch on debug mode when developing.
- We set
ALLOWED_HOSTSto accept any host in URL’s. This lazily disables Django’s Host header validation, a security feature which protects against types of attack. In a valuable application we’d set this correctly to contain only the application’s domain name.
- We set
ROOT_URLCONFso the current module (again the
__name__magic variable) will be used by Django for the URL configuration. This is normally a separate file too. Django imports the named module and accesses its
urlpatternsattribute to get the URL mapping in the application, which is why we have it later in this file.
- We set
SECRET_KEYto a random string. As the comment says, it’s needed by Django but here we aren’t using any of the features that depend on it. Currently the random key changes every time the application starts, which would cause some security features to not work properly, such as logins being invalidated. Normally we would be using the security features, so we’d set this to a single random string that we’d store securely, not in the source code, but for example, in an environment variable.
- We create a view function
index()that implements the functionality to say “hello” to the name in the URL. This is passed an instance of Django’s
HttpRequestclass, from which the URL argument “name” is retrieved, and returns an
HttpResponsewith the content we want.
- We define the
urlpatternslist which maps the empty path, equivalent to
/, to the
- We create the WSGI application object. Django can create this for us in development mode but not for deployment in production.
- We add the Python idiom
if __name__ == "__main__"to call Django’s commandline entrypoint to run when the file is executed directly. This allows us to run
python app.py runserver, as well as
python app.py --helpand any of Django’s other commands.
There’s a bit more code doing a few more things, but it’s still less than 10 steps.
This is a limited example, so it doesn’t show every way the frameworks differ. However I do think it shows some of the most important ways, especially as we consider ways to extend the application into doing something a bit more useful.
Here are seven different parts of both frameworks that I’d like to take a closer look at:
1. Application Objects
In Flask, we create an application object as our first step, and later attach our route to it. The application is the top-level “thing” we configure, and also the WSGI application that we can use in production.
If we were extending the application, we’d also attach other things like error handlers or settings to this object.
This also lets us have more than one application in the same Python process, if we ever need to.
Django, by contrast, only allows us to have a single application per process, and it’s implicitly created for us.
The top-level “thing” we are configuring is Django itself, which we do when we configure
django.conf.settings to let Django discover the other parts of the application such as the URL’s.
The WSGI application object comes across as a bit of an afterthought. Django can automatically create one for us in development, and it’s only needed for production so that common WSGI servers can run the application.
Overall here I’d say the Flask method of explicitly creating your application object and attaching things to it is clearer.
That said, once you’ve created an application in one module, in some situations Python makes it hard to import it in another file, and Flask has a “workaround” global object
flask.current_app to fix this.
Also while Flask allows multiple applications in a single process, I’ve not seen the need (except it could make certain test situations easier).
2. View Functions
This is the key code you write web applications, taking a request and returning a response. Everything a web framework does for you is, simply, either wrapping around your views or providing some kind of help for you to write those views quicker.
Our example views are short and similar, but differ in two key ways.
First, in Flask we import and use
flask.request to get the headers, while in Django we are directly passed the request.
Flask uses some magic to maintain this global variable
flask.request (a proxy object), which changes each time the function is run (and in each thread).
The first time you read the code this might surprise you, as normally in Python imported objects don’t change.
Django uses normal Python semantics, which reduces the confusion for beginners.
Global variables have been considered harmful by many programmers for a long time. While they are convenient, they’re an easy place for bugs to lurk as any part of the program could change them at any point.
Second, in Flask we return a string, while in Django we return an
Flask actually supports multiple return types, including its own response class, but Django always requires you to explicitly return a response.
This is a philosophical difference between the two. Django tends to follow Python’s TOOWTDI slogan (There’s Only One Way To Do It) (pronounced “toody”), while Flask often provides several extra shortcuts. Django does provide some shortcuts but you have to explicitly import and use them.
3. URL Configuration
Flask lets us set up URL’s with the
route() decorator, which also takes other arguments such as restricting which HTTP methods the route accepts.
Again though, Flask has more than one way to do it, with three ways to add URL’s, the last being a URL map similar to Django’s URL conf.
Django only supports a URL conf, which can add URL’s from other URL confs with
This made us write a few more lines in our single file application, but it is a single way of doing URL’s and a technique that can scale to big sites.
Our single application uses the most basic URL, so we didn’t get to see URL syntax, but I want to briefly point out both support a syntax like
Originally Django supported only regular expression based URL’s, but this can get really complicated and make beginners run for the hills.
Django version 2.0 copied Flask’s simpler URL syntax as a new, default option, so now they both work similarly.
4. Development Mode
Both frameworks support a development mode, to use when developing the application locally. Each provides a web server, automatic reloading the code changes, and debugging information in the browser when things break. Flask even includes an in-browser debugger which can be added to a Django project as well.
Flask asks us to set
FLASK_ENV=development when we run our application with
flask run in order to activate its development mode.
Another option is to set
Django only checks the settings
DEBUG flag to activate development mode, and it’s up to us how that is set.
Some projects use separate settings files for development and production, while others use in-file mechanisms, like checking an environment variable.
I prefer the latter, and that’s what we’ve done in our single file example.
This follows the well-known 12 factor methodology.
Besides this, Django also needs some boilerplate code under
if __name__ == "__main__" to support running the application.
This boilerplate still exists in larger projects in a file called
manage.py but it is generated for us when we run the
Flask avoids the boilerplate by having
flask run always work.
Going beyond basic development mode, it’s popular to extend Django’s debug mode with the django-debug-toolbar package. It displays several extra types of debug information and is well maintained by the community. There’s a similar package for Flask called flask-debugtoolbar but it seems to have fewer features and at time of writing hasn’t been updated in a year.
Both applications do some work to avoid HTML injection, by using
html.escape to safely output the URL input from the user.
This is pretty standard, and in larger applications we’d probably use a template system which would do this for us automatically (more later).
Another security feature we saw is host validation. Django has it built-in by default and we have to configure it to work, but in this example we deliberately disabled it. Flask, as far as I know, has no such feature, so while we saved doing work to configure it, it may be less secure here (it depends on how the application is deployed).
Another security feature which Django makes us think of up-front is adding a secret key.
Flask also has this concept in
Both frameworks use their keys for cryptographic functions, like encrypting user sessions, it’s just that Django requires one to start.
It’s just a rare case that our single file application doesn’t use it in any way, and while in theory Django could be changed to not require a key, it’s probably not worth it.
Both frameworks also have security pages in their documentation: Django / Flask. We can see the philosophical differences again on these pages. Django follows Python’s “batteries included” philosophy, while Flask either refers to other libraries, such as Google’s Flask-Talisman, or gives you a code snippet and asks you to finish the implementation yourself.
Django has a dedicated Security Team and security policies that detail such things as how to report security vulnerabilities without making them public. As far as I can tell, Flask has no equivalent.
6. More Batteries
Both frameworks do include some “batteries” which can help you build your application faster, although Flask only provides a minimum. Django also tends to provide its extras as pluggable backends so you can swap them out for those from third party packages, for example using an unofficial database backend.
If we had larger pages in our example, we’d probably want to use a templating system to keep the content in multiple files, and manage inserting the data without doing HTML escaping each time. Both frameworks provide template system integrations. Django has a pluggable backend system which includes support for its own Django Template Language, while Flask only supports Jinja2. Jinja2 is essentially Django Template Language rebuilt for performance by Armin Ronacher, but with enough differences that it’s not fully compatible. However Django also provides a backend for using Jinja2, so you can use it there.
Nearly every web application needs some kind of data storage. Django includes a very fully featured database layer, including migrations. Flask doesn’t include anything but it’s very common to use it with SQLAlchemy + Alembic.
Besides these examples, Django includes many extra tools beyond Flask, for things like translations, site maps, and geographic data (many are in its contrib packages).
Both frameworks have ecosystems providing extra integrated tools in third party packages.
Django has the site djangopackages which lists and compares several thousand packages.
The Flask website has a moderated list of just 68 packages.
Overall it seems there are many more Django-specific packages, although this is in keeping with Flask’s minimalist/DIY philosophy.
Overall, I prefer Django. This is probably not so very surprising, given I’m a core contributor, but I hope this has shown I have some reasoning behind this viewpoint! I must admit though I’m not a Flask expert, I’ve only worked on a few projects using it for short-term engagements, and I learned a lot about it while composing this blog post. If I’ve misrepresented anything please let me know.
According to the Python Developers Survey 2018, Flask and Django have about even usage at the moment, at 47% and 45% respectively. I think part of the preference for Flask is that it’s easy to start with just a single file, so I hope my example shows this isn’t necessarily the case. Using Django shouldn’t be hard for small applications, though there’s probably some work we can do to make it easier.
Enjoy creating web applications whichever framework you use,
Thanks to Jazeps Basko, Markus Holtermann, and Tom Grainger for reviewing this post.
Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.
One summary email a week, no spam, I pinky promise.
- The Simplest WSGI Middleware
- Where to Learn Django in 2019
- How to Score A+ for Security Headers on Your Django Website
© 2021 All rights reserved.