Building a Full Stack Web Application using Flask (Python Web Framework) - Part One
Stephen Omoregie
Posted on April 8, 2024
Python is pretty much a jack-of-all-trades programming language, with a simple syntax that makes it easily understood even for beginners. So, using its web framework (Flask) for building web applications is just as exciting and has an easier learning curve.
As a Fullstack Web developer, you will be responsible for handling both the front-end and back-end aspects of any project you're working on. This includes building user interfaces (templates), back-end logic, data modelling/persisting data in a database, third-party API integrations, or even building your own API services for others to consume.
Introduction
In this comprehensive guide, we'll be looking at how Flask helps simplify this seemingly complex skill set that is required for being a full-stack developer. We'll touch on topics like project structure, dependencies/third-party packages, Blueprints for writing modular code, file handling, database connection using MongoDB (PyMongo), User Registration/Authentication and Authorization.
Let's begin! š
Setting Up the Development Environment
I'll assume you already have python installed on your machine; I mean, if not, why not! (Download Here)
- Installing Flask and Dependencies It's always advisable to run your project using virtual environments (example, the venv module), this will help isolate project dependencies specific to your project.
First of all, create a directory/folder for your project, let's call it Tutorial.
# Step 1: Create a new virtual environment
$ cd Tutorial/
$ python3 -m venv myenv
# Step 2: Activate the virtual environment
$ source myenv/bin/activate
Note: For terminal based commands, we'll stick with the linux version now, though it'll also be similar for Windows OS.
Now with the virtual environment activated, let's install some dependencies we'll be needing for the project.
(myvenv)Tutorial$ pip3 install flask pymongo flask-login
Project Structure
While Flask is not so strict on how you MUST write your code and structure your project, it helps to have an organized structure for better code maintainability and scalability. For this project, here's a structure that I'll recommend we follow:
Tutorial/
āāā app.py
āāā blueprints/
ā āāā main_blueprint.py
āāā models/
ā āāā models.py
āāā myvenv/
āāā static/
ā āāā css/
ā āāā js/
ā āāā images/
āāā templates/
ā āāā index.html
ā āāā base.html
āāā requirements.txt
app.py
: This file will be at the root of our project directory. This file will serve as the entry point of our application. We'll instantiate the flask app in this file and other configurations.templates
: This directory will be where we store our html files that will be rendered when a page route is requested. Flask comes with built-in support for Jinja templating system which allows us to build pages with dynamic contents.static
: This directory will house the static assets needed by the application, such as css, images, javascript files. These assets will be served as needed.blueprints
: To make sure ourapp.py
file is not crowded with many routes and other configurations, we'll be separating our routes into different files and using Blueprints will help us create routes in a separate file/directory and register them on our app.models
: In this directory, we'll create the model for our user for authentication. Or any type of model you need for your specific use case. In the file models.py, we'll be connecting to our database as well as creating a class needed by Flask-login to help with Authentication / Authorization on our app.
Running a Test Server
Flask has a built-in development server for easy testing and debugging of your application. Before we start the server, create a file app.py
at the root of your project directory with the content below:
# File: Tutorial/app.py
from flask import Flask
# create an instance of the flask application
app = Flask(__name__)
# Create a route on your app
@app.route("/", strict_slashes=False, methods=["GET"])
def index():
return "<h1>This is the Home Page</h1>"
A little bit about what's going on up there
At first, we import the Flask class from the flask package. Then we create an instance of our application and save it to the variable app
, we also pass a special variable __name__
to the Flask class initialization. The __name__
argument is used to determine the root path of the application so it can find resource files relative to the location of the script. This is necessary for Flask to know where to look for resources such as templates and static files.
Next, we create a route decorator with a rule "/" that matches when our site is visited on www.example.com/
or www.example.com
(the index page), we specify strict_slashes=False
, this is to allow a more flexible url such that /about/
and /about
will be treated as the same. We also specify the HTTP Request method that the route will respond to; the methods variable receives an array of string(s) (HTTP Methods like "GET", "POST", "PUT", "DELETE"
).
Just under the route decorator, a function is defined index
which will handle the logic that determines what will be returned when the index url is requested. This can be something as simple as an HTML string (as in our example), or you can return JSON data or render a template to view.
Running the Development Server
# At the root of your project run the code
# below to start your development server
(myvenv) Tutorial$ flask run --port 5100 --debug
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5100
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 139-345-994
(myvenv) Tutorial$ flask run --port 5100 --debug
With the entry point of our application being app.py, the above command will run our application on port localhost:5100 with debug mode activate. Debug mode ensures that our application restarts whenever we make changes to the files. Meaning you don't have to kill the server and relaunch again whenever you make a change.
Testing out our web server from the terminal.
(myvenv) Tutorial$ curl localhost:5100; echo "";
<h1>This is the Home Page</h1>
(myvenv) Tutorial$
Yaay! You just took the first step in launching a functional web server using flask. You can also visit the url localhost:5100
and your server will be there to answer!
Writing Modular Code with Blueprints
Blueprints are a fundamental concept in Flask especially for structuring larger applications. They are helpful in organizing your codebase by grouping related views, templates and static files into reusable modules. This promotes maintainability, readability and separation of concerns.
We'll create two blueprints for this project. In the file main_blueprint.py
and auth_blueprint.py
and subsequently register the blueprints on the app instance in app.py
.
# File: blueprint/main_blueprint.py
from flask import Blueprint, request
main_views = Blueprint("main", __name__)
# Create routes on this blueprint instance
@main_views.get("/", strict_slashes=False)
def index():
# Define application logic for homepage
return "<h1>This is the Home Page</h1>"
@main_views.get("/profile/<string:username>", strict_slashes=False)
def profile(username):
# Define application logic for profile page
return f"<h1>Welcome {username}! This is your profile</h1>"
# File: blueprint/auth_blueprint.py
from flask import Blueprint, request
auth_views = Blueprint("auth", __name__)
# Create routes on this blueprint instance
@auth_views.route("/register", strict_slashes=False, methods=["GET", "POST"])
def register():
# Define application logic for homepage
if request.method == "POST":
# Enter logic for processing registration
return "<h1>After Registration</h1>"
return "<h1>This is the Register Page</h1>"
@auth_views.route("/login", strict_slashes=False, methods=["GET", "POST"])
def login():
# Define application logic for profile page
if request.method == "POST":
# Enter logic for processing login
return "<h1>After Login</h1>"
return "<h1>Here goes the Login Page</h1>"
Now we have to register these blueprints on the app. Let's modify the app.py
file.
from flask import Flask
from blueprints.main_blueprint import main_views
from blueprints.auth_blueprint import auth_views
# create an instance of the flask application
app = Flask(__name__)
app.register_blueprint(main_views)
app.register_blueprint(auth_views)
With the code part covered, let's try to understand what's going on in the code snippets.
auth_views = Blueprint("auth", __name__)
: Here we create an instance of the Blueprint class and store it inauth_views
variable. The first parameter is the name of the blueprint, this will be used to reference the functions defined for various routes with the url_for() function. Example url_for("auth.login"), we also pass the__name__
variable to register the location of the specific blueprint.@auth_views.route('/register', methods=["GET", "POST"])
: When defining routes, you can choose to specifically use the .get or .post or .put or .delete to restrict a function to respond to a specific HTTP request method. But using the .route method on the view, you can specify an array of methods you want the function to respond to, just like we've done with the/login
and/register
routes.from flask import Blueprint, request
: Whether you're within a blueprint or app instance file, you would need access to therequest
module, to allow you get access to the request object coming in from the user accessing your application. You can usedir(request)
within one of your route functions to see what you have access to, eg. the method, the url params, the form, files, etc. More on this later.
So far, we've been sending simple HTML Strings directly from our views, next step, we'll talk about rendering static html template with or without dynamic data.
Working with Templates (Jinja)
While it may be tempting to just write the html as strings in your function, I mean, who doesn't like the lazy way out.
No!! You will not do that! Not on my watch! Haha.
Templates offer us many great tools to create the visible part of our web application which the users will be interacting with. Our templates will be good ol' html code, but extended with the Jinja templating engine, which will allow us do things like, extending an html (as base), dynamically loading assets according to page, variables, creating DOM elements with loops, conditional rendering of elements, etc.
Here's an example of home page for our project. To focus more on the learning of templating system, we'll put part of the base.html and index.html here. You can find the source code for this project on GitHub @ Flask_App_Tutorial
The base.html template will serve as a layout that all other child templates will inherit/extend. More on this later.
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask App</title>
<link rel="stylesheet" href="{{url_for('static', filename='css/base.css')}}">
<script src="{{url_for('static', filename='js/base.js')}}" defer></script>
{% block css %}{% endblock css %}
{% block js %}{% endblock js %}
</head>
<body>
<header class="header-container">
<a href="/"><h2 class="header-title">Flask Project</h2></a>
<nav class="navigation-container">
<ul>
<a href="/login"><li class="link">Sign In</li></a>
<a href="/register"><li class="link">Sign Up</li></a>
<a href="/profile"><li class="link">Profile</li></a>
<a href="/logout"><li class="link">Sign Out</li></a>
</ul>
</nav>
</header>
<!--Here other html files that extend this base template will come here
provided the names match, as with this example body_content-->
{% block body_content %}{% endblock body_content %}
<!--You can create multiple block contents that will be rendered when the
template that extends this template with matching block name will
rendered in the block place-->
<footer class="footer-container">
<p class="footer-details">Happy Coding! Feel free to use this project as a boilerplate for your other projects</p>
</footer>
</body>
</html>
<!-- templates/index.html -->
{% extends 'base.html' %}
{% block css %}
<link rel="stylesheet" href="{{url_for("static", filename="css/index.css")}}">
{% endblock css %}
{% block js %}
<script src="{{url_for("static", filename="js/index.js")}}" defer></script>
{% endblock js %}
{% block body_content %}
<main class="main-container">
<h1 class="main-header">Welcome to the Home Page!</h1>
<p class="main-subtitle">Our little project is for learning building apps with Flask!</p>
</main>
{% endblock body_content %}
Now, let's wrap our head around what's happening in the above, it must look very different from the normal HTML you know, but let's touch some of the points.
Sure, here's a quick explanation of the templating syntax used in our Flask application:
{{ ... }}
: This is used to insert the value of a Python expression into the HTML. For example,{{ url_for('static', filename='css/base.css') }}
calls theurl_for
function with the arguments'static'
andfilename='css/base.css'
, and inserts the result into the HTML. It is also used for inserting the values of a variable dynamically into your html file(s).{% ... %}
: This is used to execute Python statements. For example,{% block css %}{% endblock css %}
defines a block that can be filled in by a child template. Theblock
statement is a control statement in the Jinja template engine (which Flask uses). It defines a block that child templates can override. For conditional blocks, you have something like{%if variable == "value" %}
Do this or that{% endif %}
{% block ... %}{% endblock ... %}
: This defines a block in the template. Blocks are sections that child templates can override. In your template, you have defined two blocks:css
andjs
for additional CSS and JavaScript files respectively, andbody_content
for the main content of the page.{{ url_for('static', filename='...') }}
: This is a Flask function that generates a URL for a static file. Flask automatically serves static files from the/static/
directory in your application's root directory. Theurl_for
function generates the correct URL to these files, regardless of how your application is deployed.
This is a Jinja template that extends base.html
. Here's a breakdown of the syntax:
{% extends 'base.html' %}
: This line tells Jinja that this template "extends"base.html
. That means it starts with the content ofbase.html
, and then fills in or replaces blocks defined inbase.html
.{% block css %}...{% endblock css %}
: This block is used to add additional CSS files specific to this page. Thelink
tag within this block is used to link theindex.css
file from the static directory.{% block js %}...{% endblock js %}
: Similar to the CSS block, this block is used to add JavaScript files specific to this page. Thescript
tag within this block is used to link theindex.js
file from the static directory.{% block body_content %}...{% endblock body_content %}
: This block is used to define the main content of the page. The HTML within this block will replace thebody_content
block inbase.html
.
Try your hands on this; practice makes great! More resources on this at the end of this guide.
Let's call it a wraps this time, we'll continue with the remaining parts of this guide where we'll learn about:
- Handling Form Submissions/File Uploads
- Downloading Files
- Connecting with a Database for Data Insertion and Retrieval using MongoDB
- User Authentication and Authorization with Flask-Login
Resources:
- Source Code: https://github.com/Cre8steveDev/Flask_App_Tutorial
- Flask Documentation: https://flask.palletsprojects.com/en/3.0.x/
- Jinja Templating Documentation: https://jinja.palletsprojects.com/en/3.1.x/
- PART TWO PUBLISHED HERE
Posted on April 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 8, 2024
April 11, 2024