Flask
Flask
https://flask.palletsprojects.com/
Flask is a microframework for python to create web applications. It basically connects the webserver with your python code, and it uses Jinja as a template engine.
So basically... it brings HTML + CSS + PYTHON + JINJA together!
Documentation
- Flask: https://flask.palletsprojects.com/en/stable/quickstart/
- Jinja: https://jinja.palletsprojects.com/en/stable/templates/
Flask was used in the past for different things at XPUB, including the XPPL, Euna's graduation work Frabjousish, the Padliography made by Kamo, and ... more!
Flask is something that you can use to make things like interactive web pages and artistic tools. It's not too difficult to work with and can do great stuff!
⚠ However ⚠
- adding layers of complexity: nginx, background service
- dynamic, not static
- relies on full server access (web-hosting is not enough)
- can quickly disappear (harder to archive compared to static pages)
- and! the trap of making something "interactive"
So never forget to keep in mind:
- how can this project be archived? (for example: static export)
- for who are you making this interactive thing?
Install
$ pip install Flask
or
$ uv venv $ uv pip install flask
Simple text serving
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
- with @app.route you can define the url flask respons to.
- the function definition after is mandetory as well as the return.
- everything that comes after return gets sent back to the browser (http GET request)
Save the code above as hello.py.
Run it with:
$ flask --app hello run
And if you want to enable the debugger (recommended!):
$ flask --app hello run --debug
Paths
You can use any route you like
@app.route("/any/route/you/like")
You can also use variable routes (example for int)
@app.route("/book/<int:id>")
def book(id):
as you can see you can grab the variable in the url through the function’s parameter
(example for string)
@app.route("/book/<bookname>")
def book(bookname):
HTTP Methods
Methods like GET or POST (DELETE, PUT…) can be handled by flask
Therefore you need to add the wanted methods to the route definition like:
from flask import Flask
app = Flask(__name__)
@app.route('/address_to_post', methods= ['POST','GET'])
def respond_to_post():
answer = ""
if request.method == 'GET':
answer = "get"
if request.method == 'POST':
answer = "post"
return answer
with request.method you can determine the incoming kind of request.
Templates
To be able return full html pages, flask uses templates using Jinja to insert variable content.
@app.route('/')
def home():
message = "Welcome Home!"
return render_template('home.html', message=message)
You can pass as many variables to a template and keep the code separate from the HTML it generates. In this example we pass message to this template:
<p>{{ message }}</p>
!important: The template file needs to be saved inside a templates folder called "templates" inside your project folder, this is a Flask default.
Helpful function
404 Page not found
@app.errorhandler(404)
def page_not_found(error):
"""Custom 404 page."""
return render_template('404.html'), 404
Examples
Observations
This prototype is prepared for class during SI30 in April 2026.
To run this example, you can use this command:
flask --app observations --debug run
Make a folder for this flask application.
Install a venv + flask in this folder:
cd /path/to/your/folder/ uv venv uv pip install flask
Save the files below in this folder, it should look like this:
.
├── observations.py
└── templates
└── observations.html
Python script
observations.py
from flask import Flask, request, render_template
app = Flask(__name__)
observations = []
@app.route("/", methods=["GET", "POST"])
def guestbook():
if request.method == "POST":
observation = request.form["observation"]
observations.append(observation)
return render_template("observations.html", observations=observations)
Template
templates/observations.html
<style>
body{
text-align: center;
}
div#archive{
background-color: lightgray;
padding: 1em;
}
div.observation{
background-color: white;
border: 1px solid black;
padding: 1em;
margin: 0.5em 0;
}
form{
margin: 1em;
}
form textarea{
width: 100%;
height: 200px;
}
form input{
margin-top: 1em;
font-size: 12pt;
}
</style>
<h1>My field observations</h1>
<div id="archive">
{% if observations %}
{% for observation in observations %}
<div class="observation">{{ observation }}</div>
{% endfor %}
{% endif %}
</div>
<form method="POST">
<textarea name="observation"></textarea>
<br>
<input type="submit" value="Save!">
</form>
Next steps
- save the observations to a observations.txt file
- read the observations from the file (not from the list anymore)
- (optional) explore other types of possible field work tools: can flask be used to annotate a map?
- (optional) install your flask application on the sandbox server: configure nginx, make a systemd file to let it run as a background process and use gunicorn for the URL routing
Deploying
Running with uwsgi
Flask warns you that using "the development server" isn't good for "production use".
Using software like uwsgi to run your flask project on a server is a good idea. It:
- Is able to deal with many users at the same time
- When configured right lets large static files (like images / videos) be served in a way that works well
https://hackersandslackers.dev/deploy-flask-uwsgi-nginx/
Making a systemd service file
When you deploy your application on a server, you need to make sure the application runs uninterrupted. If the application crashes, you'd want it to automatically restart, and if the server experiences a power outage, you'd want the application to start immediately once power is restored.
SEE: https://blog.miguelgrinberg.com/post/running-a-flask-application-as-a-service-with-systemd
Useful: https://containersolutions.github.io/runbooks/posts/linux/debug-systemd-service-units/
$ sudo nano /etc/systemd/system/YOUR_NAME_OF_THE_APP.service
[Unit] Description=<a description of your application> After=network.target [Service] User=<username> WorkingDirectory=<path to your app> ExecStart=<app start command> Restart=always [Install] WantedBy=multi-user.target
Here is an example:
[Unit]
Description=Bla Bla Bla made in Flask
After=network.target
[Service]
User=YOUR_USERNAME
WorkingDirectory=/YOUR/PATH/
ExecStart=/YOUR/PATH/.venv/bin/flask --app NAME_OF_YOUR_PY_FILE run --port 51?? #DO NOT PUT .py AT THE END + #PUT YOUR_PORT
Restart=always
[Install]
WantedBy=multi-user.target
1st thing to do when your .service file is new or changed
$ sudo systemctl daemon-reload
Then in order:
$ sudo systemctl start YOUR_NAME_OF_THE_APP $ sudo systemctl status YOUR_NAME_OF_THE_APP
When you see that it's working (checking its status, that it actually is running, etc...)
$ sudo systemctl enable YOUR_NAME_OF_THE_APP
Will make the "service" auto start when the pi restarts.
Additional commands:
$ sudo systemctl restart YOUR_NAME_OF_THE_APP $ sudo systemctl stop YOUR_NAME_OF_THE_APP
To view the log file (errors):
$ sudo journalctl -u YOUR_NAME_OF_THE_APP -f
You can find documentation here:
https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
Boilerplates
You can use these examples as boilerplates for your Flask application!
- Install Flask
- Copy a Flask boilerplate and save it as a Python script (
filename.py) somewhere - Run the Flask application with:
flask --app filename run --debug - Open the Flask application in your browser at: http://localhost:5000 (5000 is the default port Flask uses)
from flask import Flask, request
app = Flask(__name__)
html_template = """
<h1>transformations</h1>
<form action="/" method="post">
<input type="text" name="search">
<br><br>
<input type="submit" value="transform">
</form>
"""
@app.route("/", methods=["POST","GET"])
def transformations():
if request.method == "POST":
search = request.form["search"]
result_list = []
for character in search:
unicode_point = format(ord(character))
result_list.append(character + " " + unicode_point)
result_string = "<br>\n".join(result_list)
return html_template + f"<pre>{ result_string }</pre>"
if request.method == "GET":
return html_template
Running Flask applications on the collective hub.xpub.nl servers
gunicorn
First install gunicorn and python-dotenv:
$ pip3 install gunicorn python-dotenv
And then make or change the following files:
Makefile
include .env
export
default: local
local:
@flask run --debug
breadcube:
@SCRIPT_NAME=${APPLICATION_ROOT} gunicorn -b localhost:${PORTNUMBER} --reload app:app
.env
APPLICATION_ROOT=/breadcube/overlap PORTNUMBER=5000
settings.py
import os
from dotenv import main
# Load environment variables from the .env file
main.load_dotenv()
# Bind them to Python variables
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/')
PORTNUMBER = int(os.environ.get('PORTNUMBER', 5000))
app.py
# load the settings of the applicaiton
# (to handle the routing correctly)
app.config.from_pyfile('settings.py')
nginx
# overlap flask application
location ^~ /overlap/static/ {
alias /var/www/html/breadbrick/SI21/week5/static/;
autoindex on;
}
location ^~ /overlap/ {
proxy_pass http://localhost:5000/breadcube/overlap/;
}
Other ways to do deployment
- for Kamo's notes: https://git.xpub.nl/kamo/pad-bis#nginx-configuration
- Flask official deployment page: https://flask.palletsprojects.com/en/stable/deploying/