Building a Simple Web App with Flask 2.0 — Part 1
Flask is an extremely popular web framework for the Python language. It’s considered a micro web framework, meaning it’s minimalistic and lacks built-in support for a lot of the functionality one would expect from a full-stack framework. However, the Flask framework is extensible and, through the use of extensions, can be made to support most if not all of the core functionality required of a full stack web application.
A Note Before Beginning:
I’m human, and therefore prone to error. So is everyone else, including, frighteningly enough, all the other people writing code tutorials online. It’s very easy to fail, through ignorance or carelessness, to adhere to best practices or to overlook a minor mistake that may not create an error now might in the future, or to simply write bad but functional code. The more you learn and hone your skills, the more you’ll be able to spot these errors when you or others make them, and then correct them— hopefully in a constructive manner, with plenty of compassion for yourself and your teammates. It’s still worthwhile to sit down at your computer and create imperfect things, but be mindful of the fact that you can’t always trust code you find online, especially code that comes from unknown or unvetted sources. Even the most popular guides, teachers, and tutorial sites can and do get it wrong. Sometimes very wrong. It’s never a good idea to rely on a single source for almost any kind of information. As you proceed, be aware that this is written by and for someone just starting out with Flask. You should sit down, follow the tutorial, write your own code fearlessly, and when you inevitably find yourself faced with a bug or some unanticipated problem, be prepared to practice the real skill of programming: invest your time, do your research and find creative solutions to get something up and running. It’s rarely the case that code can’t be fixed or revised, especially if you deploy early and test often. Provided you’ve allowed yourself enough time to complete your project by your deadline, you should be ok. This process of trial and error is how you gain experience and build your skillset. Learning what not to do is as important as learning what you should do, and always, of course, why. Remember that in almost all cases, it’s better to have something than nothing, so don’t quit even if it becomes apparent that you’re not going to meet your target. Breathe deep and keep going.
A final thought:
Treat a code tutorial like a recipe — read (or skim) the whole thing, or at least the summary, before you begin in order to make sure you have all the tools you need to follow along (and that those tools/versions being used are relevant to what you’re actually trying to accomplish), and, crucially, to make sure the person who created the tutorial actually finished it. It’s no fun to find yourself 2/3 of the way through a multi-video, many hour instructional playlist only to find that you aren’t going to be able to complete it because the guide is unfinished, undergoing editing, or has sections missing. That’s especially true when you were relying on that tutorial to guide you through some time sensitive assignment. Digital content is not static, so don’t just assume that everything you need will be there. It’s happened to me, it’s happened to others, and if you’re teaching yourself almost any new technology, it will likely happen to you at some point. Reduce the risk of being caught out by reading ahead.
Now, your first Flask app:
This tutorial will simply guide you through the process of creating a basic web app with Flask and deploying it to Heroku. It DOES NOT include database functionality. Although I’ll attempt to be as comprehensive as possible, this tutorial assumes you already have the requisite knowledge of the Python language — here we’ll be using Python 3.9.5— and at least a fundamental knowledge of web development. My goal is to be beginner friendly, but as always, if you come to the table with at some background knowledge, you’ll be far more capable of filling in any gaps and elaborating on what you see here.
All terminal commands given are based on zsh. If you’re using a different terminal, it may be necessary to research the correct commands in some places, but the principles will remain the same. I use VS Code and will assume you do the same. With a few potential exceptions, any appropriate IDE should work as well as another. Where differences occur, they should be minor and easy to work around for someone with working knowledge of their chosen IDE.
To begin, open your terminal and create a folder for the tutorial project, then navigate into that folder.
mkdir flaskTut; cd flaskTut
Once here, you’ll set up a virtual environment to work in. This can be named whatever you like.
python3 -m venv tutenv
A virtual environment is a Python environment that allows you to isolate the dependencies you’re working with to that environment. What that means is that if you’re working on multiple projects that use the same package in different versions, you can install those differing versions without creating any conflicts, provided you’ve done so in different environments.
Now, activate the virtual environment
source tutenv/bin/activate
If the previous steps were followed successfully, you’ll now be working in your virtual environment and will see your terminal prompt preceded by the name of the virtual environment in parentheses. Mine appears as follows:
(tutenv) tonyshifflett@Tonys-MacBook-Pro tutenv%
As long as you see something similar (or are using a customized prompt that’s expected to appear differently) you should be fine. When you’re ready to end your work session or want to switch environments, the command to deactivate it is exactly that:
deactivate
Don’t do that yet, though. (No harm done if you have. Just reactivate it with the command from above.)
Now, we’ll install our dependencies.
pip3 install flask
pip3 install gunicorn
That’s it. You’ve done it. Save your dependencies for distribution with the command
pip3 freeze > requirements.txt
Once you’ve done that, open the file and have a look. You should see something like the following:
click==8.0.1Flask==2.0.1 gunicorn==20.1.0 itsdangerous==2.0.1Jinja2==3.0.1MarkupSafe==2.0.1Werkzeug==2.0.1
This is it. That’s all there is to a basic Flask app. You’ll also notice that absolutely no boilerplate, templates, or files of any kind have been created other than the requirements.txt file you made yourself. Flask leaves all of this up to you, the developer. It significantly reduces the overhead of creating a simple app, with no need to delete extraneous files or clear out unnecessary code as sometimes needs to be done with other frameworks. The downside is that, for a beginner, the lack of an opinionated framework for executing more complex functionality can make it difficult to elaborate on the basics if you’re not already familiar. For that reason among many other excellent ones, it’s always important to know where to find the official documentation and how to read it. In the case of Flask, you can find it here:
There are many good tutorials online for going further with your development, but be mindful of version compatibility. Because so much complex functionality requires the use of extensions, you need to be sure that the tools you’re using are all compatible with each other and with the latest version of Flask (or whichever one you’ve chosen). As discussed above, version compatibility is an excellent reason for using virtual environments in the first place. If you continue with Flask, you’re likely to find tutorials using extensions to provide things like database functionality (again, not covered here) which aren’t compatible with the latest version of Flask and would require you to use a previous version if you wanted to follow along. These topics are beyond the scope of this article, but it’s worth mentioning as a potential stumbling block down the road.
Now that you’ve saved your dependencies to the requirements.txt file, it’s as good a time as any to initialize your local repo and create your .gitignore and starter files.
git init
touch .gitignore runtime.txt Procfile app.py
While you’re here, check the name of your current branch.
git branch
It’s now the convention for your production branch to be called “main”, whereas it was previously called “master”. If your branch is master, you can change it like so:
git branch -m "main"
Depending on the scope of your project, it will likely be appropriate to create a “dev” branch, as well as individual feature branches while working. Because this is a tutorial, and a reasonably uncomplicated one at that, we won’t dive too deeply into the topic; just be aware that there are in fact best practices when it comes to version control, and if you’re not familiar with them you should save yourself a lot of trouble and make the effort to learn more.
Now you’ll open your files in VS Code, if you haven’t already done so, with the command
code .
This will open your directory in a VS Code window, and allow you to open tabs for the files you’ve written. You can also create files and directories directly in VS Code now, or continue using the command line. I prefer the command line, but as long as you are in fact comfortable with the command line (which you absolutely must be, or become), feel free to do whatever is easiest for you. If you’e a beginner, I honestly believe it’s worth a little toggling and a few more key strokes to practice working in your terminal. It’s an essential skill for any developer, and a little extra time now will serve you well later.
In VS Code, or via the terminal’s ls command, review your file names and be sure you’ve made them correctly. It matters. .gitignore begins with a dot. Procfile has an initial capitalization and no file extension.
You should write your Procfile as follows:
web: gunicorn app:app
As mentioned above, the ultimate goal is to deploy our app to Heroku, and this file will tell Heroku how to do it.The single line instructs Heroku to start a web server for us using gunicorn; and that the app file is called app(app.py).
Save the file and close it. In runtime.txt, you need a single line telling Heroku which version of Python to use in order to run the app.
python-3.9.5
You can save and close this file as well.
In .gitignore, you’ll add the following:
tutenv/
This will inform git not to track your virtual environment directory and any subdirectories.
You may as well save and commit these files at this point before moving on. It’s always good practice to make your git commits frequently (within reason), and especially after any meaningful change. Doing so allows you to have a version of your files available at multiple stages of development, rather than making an initial commit, writing nearly a full program, encountering some error, and finding you have no intermediate stage to go back to. Failing to make frequent, logical, and well-commented commits defeats the entire purpose of version control by removing the safeguards against losing unnecessarily large chunks of work. I won’t remind you again, but as you move through the tutorial and write your code, ask yourself if you should make a commit at this or that point. If you’ve implemented some new function, made a new import, written some meaningful piece of code, or anything else that constitutes a discrete logical unit within your program, the answer is that you should probably commit, with a message describing what new feature or change has been implemented. This way, you’ll have a version of your program before a small change, and a version after, and the next one, and the next one, so that if you need to go back to an earlier stage of development because of a hard-to-find bug or otherwise broken code, you don’t lose more than is absolutely necessary. Concise, semantic commit messages will allow you or others to see what changed between commits and will facilitate finding the appropriate earlier version, if the time comes. This is vitally important, and if you ever forget and lose your, work, all I can say is:
Now, with those files saved, closed, and committed to your repo, open app.py.
from flask import Flaskapp = Flask(__name__)
if __name__=='__main__':
app.run(debug=True)
These four lines of code provide a working Flask app. To prove it, save the file and, in your terminal, use the command
python3 app.py
and visit localhost:5000. You should connect and receive a 404 error. Since the app is currently empty, there’s nothing to find at that URL, but the connection is possible since the app is running.
Let’s break things down a bit, so we understand what’s going on.
from flask import Flask
This line imports the Flask class to app.py. The app itself is an instance of this class. The following line
app = Flask(__name__)
is where we declare app as an object of the class. The argument __name__ is a built in variable that evaluates to the name of the module. Flask uses helper functions that accept the module name and use it to find various subdirectories. __name__ is a convenient shorthand for the name of the current module, and unlike if you were to hard-code that name in, is more fluid when the interpreter is working with it, which becomes important in several circumstances.
if __name___ = "__main__":
app.run(debug = True)
When a Python module is being run as a standalone (i.e, not being imported into another module), __name__ assigned the special value “__main__” rather than the actual name of the module. In essence, this snippet dictates that if the current module is being run, in which case the condition will evaluate as True, the app’s run() method is called, which of course causes the app to run. The debug variable is set to True here, which is useful in a development environment as it allows access to error details. However, it makes those details easily accessible and also creates severe security vulnerabilities, so in a production environment, it’s necessary to set this to False. You should not deploy your code with the debug variable set to True. You should not deploy your code with the debug variable set to True. Do not deploy your code with the debug variable set to True.
Incidentally, the method we’ve used here creates a global instance of Flask. According to the official documentation, this is fine for simple apps like ours, but may be inappropriate for larger, more complex apps. I highly recommend reading that documentation to learn how to do it another way in the future, when you’re ready to move on to larger Flask projects.
Now, we have a very basic Flask app that does absolutely nothing. It’s an accomplishment, but not very exciting. We’re going to write our first route.
from flask import Flaskapp = Flask(__name__)@app.route('/'):
def landing():
return "<h1>This is our landing page<h1>"if __name__=='__main__':
app.run(debug=True)
Save the file, and now rerun it. When you revisit localhost:5000, you should see the h1 rendered on the page. And, that’s it. You’ve now created a working, barebones Flask app.
What we’ve done here is created our index route. The line
@app.route('/'):
is what’s known as a decorator. Decorators, simply put, are functions that take another function and extend its behavior without modifying the actual definition of that function. In this case, the function being modified is Flask’s index() function. The string argument is the url rule, which is the pattern the incoming request has to match to trigger the view function. In our case, the view function is
def landing():
return "<h1>This is our landing page</h1>"
One triggered, the function returns the specified value, which is rendered as HTML.
We’ll pause here, but in the upcoming Part Two, we’ll discuss how to use jsonify and templating with Jinja2 in order to achieve greater functionality.