Flask vs Django: Comparing REST API Creation

If you were to search for the “best programming language to learn” on Google, it would tell you that Python is one of the most frequently recommended languages for beginners. This should come as no surprise, since Python is one of the most user-friendly languages on the market today and forms the foundation of companies like Instagram, Pinterest, and Spotify.

When it comes to actually building an application in Python, there is no shortage of choices in the toolsets that you can use. In the web-driven world that we currently live in, APIs are king, and when it comes to Python, there are two popular choices for building a scalable, performant REST API: Django and Flask.

 

Crafting a Fortune Teller API

The two most popular frameworks for Python, Django and Flask, take incredibly different approaches to web development. Django, the older of the two frameworks, is often called a “batteries included” framework, meaning that it contains just about everything you need to launch a full featured application in no time flat. Flask, on the other hand, is a highly extensible “micro-framework” that launches with a bare minimum set of features, but has a thriving plugin ecosystem which allows developers to only include the functionality that they need to succeed.

In order to demonstrate the differences between these two frameworks, let’s take a look at the process behind spinning up a basic “Fortune Cookie” REST API using each one. To keep things simple, this API will have only one endpoint, /fortune, that will return a basic JSON response containing a random fortune. 

That’s it. Nothing too fancy, but enough to get the gist of the complexities (or simplicities) of each framework.

If you want to follow along with the examples, make sure you have a recent version of Python installed along with Flask and Django. To get started quickly, you can either:

  • Create a custom Python runtime (click on the Get Started button once you log in) with just the packages required (i.e., Flask and Django), and then automatically install it into a virtual environment using ActiveState’s CLI, the State Tool.

From here on out, this article assumes that you have Flask and Django along with Python installed on your system and in your PATH.

 

API Creation with Flask

Let’s go ahead and make our API endpoint. To do this, let’s first start with a very basic /fortune endpoint that returns only one fortune. First, create a new file called app.py in your favorite text editor, and enter the following code:

from flask import Flask, jsonify

app = Flask(__name__)
app.config["DEBUG"] = True

@app.route('/fortune', methods=['GET'])
def fortune():
  return jsonify({
 'data': 'How many of you believe in psycho-kinesis? Raise my hand.',
 'status': 'awesome'
  }), 200

To break the above block of code down, what we’re doing is defining a route which, in web application terms, is the part that comes after the domain name, such as the /fortune in https://api.flower.codes/fortune. The bit that comes after, the def fortune(), is a function that processes requests to the /fortune route, which currently returns a single fortune.

Let’s make things a little more interesting and add a few more fortunes which we will select from at random to provide that unpredictable, fortuney goodness that we expect. To do this, we’ll mix in a little bit of Python magic, and add a few more bad fortune cookie proverbs:

from flask import Flask, jsonify
import random

app = Flask(__name__)
app.config["DEBUG"] = True

@app.route('/fortune', methods=['GET'])
def fortune():
    fortunes = [
      'A feather in the hand is better than a bird in the air. ',
      'A golden egg of opportunity falls into your lap this month.',
      'Bide your time, for success is near.',
      'Curiosity kills boredom. Nothing can kill curiosity.',
      'Disbelief destroys the magic.',
      'Don't just spend time. Invest it.',
      'Every wise man started out by asking many questions.',
      'Fortune Not Found: Abort, Retry, Ignore?',
      'Good to begin well, better to end well.',
      'How many of you believe in psycho-kinesis? Raise my hand.',
      'Imagination rules the world.',
      'Keep your face to the sunshine and you will never see 
shadows.',
      'Listen to everyone. Ideas come from everywhere.',
      'Man is born to live and not prepared to live.',
      'No one can walk backwards into the future.',
      'One of the first things you should look for in a problem is its positive side.',
      'Pick battles big enough to matter, small enough to win.',
      'Remember the birthday but never the age.',
      'Success is failure turned inside out.',
      'The harder you work, the luckier you get.',
      'Use your eloquence where it will do the most good.',
      'What's hidden in an empty box?',
      'Your reputation is your wealth.'
    ]

  return jsonify({
 'data': random.choice(fortunes),
 'status': 'awesome'
  }), 200

Now all we have to do is start the API, which can be accomplished using the flask run command (don’t forget to set the FLASK_APP variable to the app.py file we just created):

$ FLASK_APP=app.py flask run
 * Serving Flask app "app.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

If we were to take the listed domain name and append our defined route, which is http://127.0.0.1:5000/fortune, we should see a random fortune response in the JSON format we defined:

Flask JSON response

To test out the randomness, try reloading the page. You should get something different every time, and you will also be able to check “create a REST API” off of your bucket list

 

API Creation with Django

As we just saw, Flask is a very hands-off framework. It is minimally opinionated, and gives its users enough rope to hang themselves if they’re not careful. But what about Django? What makes it different? In a nutshell: features.

To understand what I mean by this, let’s first create a project. This will be different than the Flask example because Django projects are far more robust, which means that their scaffolding must be generated to get started instead of simply adding a few lines of code to a single .py file:

$ django-admin startproject activestate_django_api_example

The above command creates a basic directory structure that defines our Django application. If you take a look at your file system, you should see a new folder based on the name of the project that you just created, with a handful of other files nested underneath.

+-- activestate_django_api_example
 +-- manage.py
 +-- activestate_django_api_example
     +-- __init__.py
     +-- settings.py
     +-- urls.py
     +-- wsgi.py

While each of these files can be used to modify the configuration and functionality of your application, we will be focusing primarily on the urls.py file for the purposes of this demo. Before we can start building out our API, however, we must run the built-in database migrations first:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

These migrations initialize the database tables that are necessary for authentication, session management, and the admin interface (something that Flask does not ship with, for those of you who are following along at home).

To start the server as-is, all you have to do is run python manage.py runserver, which will start the server on port 8000 of your local machine:

$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
August 14, 2019 - 19:07:37
Django version 1.11.23, using settings 'activestate_django_api_example.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

As mentioned, Django ships with a built-in admin interface. So if we were to navigate to http://127.0.0.1:8000/admin, we would see a page that looks similar to the following:

Django Amin Console

If, for example, we want to actually use the admin interface, we might want to create an admin user, like so:

$ python manage.py createsuperuser
Username (leave blank to use 'zach'): zach
Email address: zach@flower.codes
Password:
Password (again):
Superuser created successfully.

Utilizing the admin interface is outside the scope of this article, but it is a great example of just how full-featured Django is in comparison to Flask (by design). The next thing that we need to do in order to create our Fortune API is to actually create the Fortune application in our Django project:

$ python manage.py startapp fortune

When run, the above command will create a new folder in our project directory called “fortune”, which will contain a handful of files that are similar to the ones that were already created:

+-- activestate_django_api_example
 +-- db.sqlite3
 +-- manage.py
 +-- activestate_django_api_example
     +-- __init__.py
     +-- settings.py
     +-- urls.py
     +-- wsgi.py
 +-- fortune
     +-- __init__.py
     +-- admin.py
     +-- apps.py
     +-- models.py
     +-- tests.py
     +-- views.py
     +-- migrations
         +-- __init__.py

Django follows the Model-View-Controller standard, which means that in order to create our /fortune API endpoint in Django we will have to add it to the views.py file. Since we worked out the random fortune selection in our Flask example above, we will skip that step and go straight to the full functionality:

from django.shortcuts import render
from django.http import JsonResponse
import random

# Create your views here.
def fortune(request):
  fortunes = [
 'A feather in the hand is better than a bird in the air. ',
 'A golden egg of opportunity falls into your lap this month.',
 'Bide your time, for success is near.',
 'Curiosity kills boredom. Nothing can kill curiosity.',
 'Disbelief destroys the magic.',
 'Don't just spend time. Invest it.',
 'Every wise man started out by asking many questions.',
 'Fortune Not Found: Abort, Retry, Ignore?',
 'Good to begin well, better to end well.',
 'How many of you believe in psycho-kinesis? Raise my hand.',
 'Imagination rules the world.',
 'Keep your face to the sunshine and you will never see shadows.',
 'Listen to everyone. Ideas come from everywhere.',
 'Man is born to live and not prepared to live.',
 'No one can walk backwards into the future.',
 'One of the first things you should look for in a problem is its positive side.',
 'Pick battles big enough to matter, small enough to win.',
 'Remember the birthday but never the age.',
 'Success is failure turned inside out.',
 'The harder you work, the luckier you get.',
 'Use your eloquence where it will do the most good.',
 'What's hidden in an empty box?',
 'Your reputation is your wealth.'
  ]

  return JsonResponse({
 'data': random.choice(fortunes),
 'status': 'awesome'
  })

As in our Flask example, our fortune() method in our Django view will return a JSON response containing a random fortune and a basic status response. Next, we need to define our routes. While Flask handles this inline in one file, Django prefers to keep routes in a urls.py file. Since this file is not included in the fortune application scaffolding, we will first want to create it and add the following code:

from django.conf.urls import url
from . import views

urlpatterns = [
  url(r'^$', views.fortune, name='fortune'),
]

This code defines URL patterns that we want to match on and the relevant methods that get returned when those patterns are matched. In this example, the root route will execute our fortune view that was defined above. However, it’s important to understand that these routes haven’t been enabled yet. To do that, we will need to update the primary urls.py file:

from django.conf.urls import url
from django.contrib import admin
from django.conf.urls import include

urlpatterns = [
 url(r'^admin/', admin.site.urls),
 url(r'^fortune/', include('fortune.urls')),
]

As you can see, our new urls.py file has been included underneath the fortune/ namespace in our primary routes file. This means that while our fortune/urls.py file indicates that only the root path should execute our fortune() method, this root path is actually scoped underneath the fortune/ path as dictated by our primary routes file. If we were to then visit our new http://127.0.0.1:8000/fortune API endpoint, we should see a response that looks similar to our Flask example.

Django JSON Response

 

Django vs Flask – Which is Better?

Given our Fortune example, you’d be hard-pressed to find anyone who would argue that the Django solution is simpler than the Flask one. But these two frameworks are stunningly different by design. Django is an incredibly robust web application solution that isn’t suited to building REST APIs alone. You can do just about anything with Django (almost) out of the box, which makes it the perfect choice for developers who know exactly what they want to build, and don’t want to mess with building standard components from the ground up.

Flask, on the other hand, is proudly marketed as a micro-framework. It’s not intended to be a Django competitor, but rather a better alternative for developers who want fine-grained control over the design and development of their application. A great system for any new developer who wants to learn the ins-and-outs of web applications, Flask is also a powerful tool that offers just enough structure to move quickly, while at the same time offering the right amount of stability for experienced developers to build anything they want without having to make design or implementation compromises.

Zachary Flower

Zachary Flower

Zachary Flower (@zachflower) is a Fixate IO Contributor, principal engineer at Automox—a Boulder-based patch management company—and freelance writer. With a passion for simplicity and usability within the development pipeline, Zach puts a strong emphasis on the importance of documentation, developer productivity, and shift-left testing strategies.