Elena' s AI Blog

Joking Flask App

10 Dec 2023 (updated: 02 May 2026) / 21 minutes to read

Elena Daehnhardt


Midjourney, December 2023

If you click an affiliate link and subsequently make a purchase, I will earn a small commission at no additional cost (you pay nothing extra). This is important for promoting tools I like and supporting my blogging.

I thoroughly check the affiliated products' functionality and use them myself to ensure high-quality content for my readers. Thank you very much for motivating me to write.



TL;DR:
  • Build Flask apps: use @app.route() for URL endpoints, render_template() with Jinja2 templates, and serve CSS/images from the static/ folder. Always import 'request' before handling forms. Start Flask with 'flask run' or 'python app.py', and never run the built-in dev server in production.

Previous: Part 1 β€” Loop like a Pro with Python Iterators

Next: Part 3 β€” What is Docker?

Introduction

Flask is the closest thing Python has to a magic trick: a few lines of code and you have a working web server. In this post I will show you how to build a small web application that serves random jokes from a text file β€” perfect for learning the core ideas without drowning in boilerplate.

We will cover:

  • Installing Flask and setting up a virtual environment
  • Routing URLs to Python functions
  • Using Jinja2 templates to render HTML
  • Serving static files (CSS, images)
  • Handling form submissions
  • Wiring it all together into a working Joker App

The full source code is in the GitHub repository flask-random-joke.

Python Flask

Flask is a lightweight WSGI web framework for Python. It gives you routing, a templating engine, and a development server β€” nothing more, nothing less. This minimalism is exactly why it is a great first framework: you understand every line you write.

Flask is built on two libraries: Werkzeug (the WSGI toolkit that handles HTTP requests and responses) and Jinja2 (the templating engine). Both are worth knowing a little about even if Flask abstracts them away.

Installation

Global installation

Install Flask with pip:

pip install Flask

A global install places the package in your Python interpreter’s site-packages directory. Typical locations:

  • Windows: C:\Users\<Username>\AppData\Local\Programs\Python\Python<version>\Lib\site-packages
  • macOS/Linux: /usr/local/lib/python<version>/site-packages

Global installs are convenient for quick experiments, but they quickly lead to dependency conflicts when different projects need different package versions. For anything beyond a quick prototype, use a virtual environment.

Virtual environments with venv

venv is Python’s built-in tool for creating isolated environments. Each environment has its own Python interpreter and its own site-packages β€” projects can no longer step on each other’s toes.

Create and activate a virtual environment:

# Create
python3 -m venv venv

# Activate β€” macOS/Linux
source venv/bin/activate

# Activate β€” Windows
venv\Scripts\activate

Your prompt will change to show (venv), confirming the environment is active. Now install Flask inside it:

pip install Flask

To leave the environment:

deactivate

venv has been part of the Python standard library since Python 3.3 and is the recommended choice over the older virtualenv package. See the official venv docs for the full reference.

PyCharm IDE setup

If you use PyCharm (I do β€” it makes daily Python work much smoother), the IDE can manage your virtual environment for you:

  1. Create a virtual environment β€” open the built-in terminal and run python3 -m venv venv.
  2. Install Flask β€” with the venv active, run pip install Flask.
  3. Configure the interpreter β€” go to File β†’ Settings (or PyCharm β†’ Preferences on macOS), then Project β†’ Python Interpreter. Click the gear icon β†’ Add…, choose Existing environment, and point it to venv/bin/python (macOS/Linux) or venv\Scripts\python.exe (Windows).
  4. Start a Flask project β€” go to File β†’ New Project, set the type to Flask, and select the interpreter you just configured. PyCharm will scaffold a minimal β€œHello World” app for you automatically.
Interpreter Settings, adding a package

Interpreter Settings β€” adding a package in PyCharm

I also use the Tabnine plugin for rapid prototyping. It suggests completions as you type, which speeds up the parts of coding that are mostly muscle memory.

Joker Flask App

Let us build the app step by step, starting from the simplest possible Flask application and adding features one by one.

Step 1 β€” Hello World

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(debug=True)

What is happening here:

  • Flask(__name__) creates the application object. __name__ tells Flask where to look for templates and static files relative to this module.
  • @app.route('/') is a decorator that maps the root URL / to the hello_world function. Whenever someone visits /, Flask calls the function and returns its result as the HTTP response.
  • app.run(debug=True) starts the development server with auto-reloading and an interactive debugger. Never use debug=True in production β€” it exposes a code-execution interface in the browser.

Run it:

python app.py

Visit http://127.0.0.1:5000/ to see β€œHello, World!”.

Flask Hello World!

Flask Hello World!

You will also see a warning in the terminal: β€œWARNING: This is a development server. Do not use it in a production deployment.” This is correct β€” Flask’s built-in server is single-threaded and not hardened for real traffic. For production, use a proper WSGI server such as Gunicorn:

gunicorn -w 4 -b 0.0.0.0:5000 app:app

The -w 4 flag starts 4 worker processes. Replace app:app with <module_name>:<flask_app_variable> to match your file name.

Step 2 β€” Adding More Routes

Add an /about route alongside the root:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/about')
def about():
    return 'This is the about page.'

if __name__ == '__main__':
    app.run(debug=True)

Each @app.route() decorator registers a URL pattern. The function name (about) becomes the endpoint name, which you can reference later with url_for('about') to generate the URL programmatically β€” useful when URLs change.

Step 3 β€” URL Variables

Capture dynamic values directly from the URL:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/about')
def about():
    return 'This is the about page.'

@app.route('/user/<username>')
def show_user_profile(username):
    return f'Hello, {username}!'

if __name__ == '__main__':
    app.run(debug=True)

The <username> part in the route pattern captures whatever appears in that position and passes it as a function argument. Visit http://127.0.0.1:5000/user/Elena to see it in action.

You can also type-convert URL variables: <int:user_id>, <float:price>, <path:subpath>. Flask will return a 404 automatically if the value does not match the converter.

Step 4 β€” Jinja2 Templates

Returning raw strings from route functions gets messy fast. Flask’s render_template() function loads an HTML file from the templates/ directory and fills in dynamic placeholders using the Jinja2 engine.

Create the folder structure:

/project
    /templates
        index.html
    app.py

templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ message }}</h1>
</body>
</html>

Jinja2 syntax at a glance:

Syntax Purpose
{{ variable }} Output a value
{% if condition %} Conditional block
{% for item in list %} Loop
{% block name %} Template inheritance block

Update app.py to render the template:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
    return render_template('index.html', title='Home', message='Hello, World!')

if __name__ == '__main__':
    app.run(debug=True)

render_template finds templates/index.html, replaces {{ title }} with 'Home' and {{ message }} with 'Hello, World!', and returns the resulting HTML string to the browser.

Step 5 β€” Static Files (CSS)

Static files β€” stylesheets, scripts, images β€” live in a folder called static/. Flask serves them automatically at /static/<filename>.

Add a stylesheet:

/project
    /static
        style.css
    /templates
        index.html
    app.py

static/style.css:

body {
    font-family: 'Arial', sans-serif;
    margin: 20px;
    background-color: #111;
    color: #eee;
}

h1 {
    color: forestgreen;
    text-align: center;
}

button {
    display: block;
    margin: 1em auto;
    padding: 0.5em 1.5em;
    background-color: forestgreen;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
}

button:hover {
    background-color: #2e8b57;
}

Link it in index.html using Flask’s url_for() helper, which generates the correct URL regardless of where your app is mounted:

<head>
    <!-- other meta tags -->
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>

Always use url_for('static', filename='...') rather than hard-coding /static/style.css. If your app moves to a subdirectory, the hard-coded path breaks; url_for does not.

Joker App got a Style

Joker App with a stylesheet applied

Step 6 β€” Handling Forms

Flask exposes incoming request data through the request object. A common beginner mistake β€” which we will reproduce on purpose β€” is forgetting to import it.

app.py (with the import error still lurking):

from flask import Flask, render_template  # request is missing here

app = Flask(__name__)

@app.route('/greeting', methods=['GET', 'POST'])
def greeting():
    if request.method == 'POST':           # NameError here!
        username = request.form['username']
        return f'Hello, {username}!'
    return render_template('greeting.html')

if __name__ == '__main__':
    app.run(debug=True)

templates/greeting.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Greeting</title>
</head>
<body>
    <form method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required>
        <button type="submit">Say Hello</button>
    </form>
</body>
</html>

Submit the form and you get:

Internal Server Error β€” NameError: name 'request' is not defined

Internal Server Error β€” NameError: name 'request' is not defined

The error message is your friend: NameError: name 'request' is not defined. We forgot to import request. Fix it:

from flask import Flask, render_template, request  # request added

The methods=['GET', 'POST'] argument on the route is important: by default Flask only accepts GET requests. If you try to POST to a route without declaring it, you get a 405 Method Not Allowed.

Step 7 β€” The Joke App

Now let us put it all together. Create a jokes.txt file β€” one joke per line β€” and wire up a route that picks one at random.

jokes.txt (a few examples to get started):

Why do programmers prefer dark mode? Because light attracts bugs.
A SQL query walks into a bar, walks up to two tables and asks: Can I join you?
Why do Python programmers wear glasses? Because they can't C.
There are 10 types of people in the world: those who understand binary, and those who don't.

Final app.py:

from flask import Flask, render_template, request
import random

app = Flask(__name__)


def get_random_joke():
    """Read all jokes from jokes.txt and return one at random."""
    with open('jokes.txt', 'r') as file:
        jokes = [line.strip() for line in file if line.strip()]
    return random.choice(jokes)


@app.route('/')
def hello_world():
    return render_template('index.html', title='Home', message='Hello, World!')


@app.route('/about')
def about():
    return 'This is the about page.'


@app.route('/user/<username>')
def show_user_profile(username):
    return f'Hello, {username}!'


@app.route('/greeting', methods=['GET', 'POST'])
def greeting():
    if request.method == 'POST':
        username = request.form['username']
        return f'Hello, {username}!'
    return render_template('greeting.html')


@app.route('/joke')
def random_joke():
    joke = get_random_joke()
    return render_template('joke.html', joke=joke)


if __name__ == '__main__':
    app.run(debug=True)

Two small improvements over the original:

  1. line.strip() β€” strips trailing newlines and whitespace so jokes do not have \n hanging off the end when displayed.
  2. if line.strip() β€” skips blank lines in jokes.txt, so an accidental empty line at the bottom of the file does not produce an empty joke.

templates/joke.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Joke</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>{{ joke }}</h1>
    <form method="get" action="{{ url_for('random_joke') }}">
        <button type="submit">Get Another Joke</button>
    </form>
</body>
</html>

Visit http://127.0.0.1:5000/joke β€” you should see a fresh joke every time you click the button.

ChatGPT prompts I used

Even with prior Flask experience I reached for ChatGPT to speed things up β€” generating the jokes.txt content and drafting some CSS in particular. A few prompts that worked well:

Output 200 strings containing concise and funny Computer Science, Machine Learning, AI, and Python jokes

Rewrite joke.html and add a button to refresh the page and show another joke

Write a CSS style to centre this button and make it look clean

Add comments and explain the code I am going to paste next

Write the simplest style.css for this Flask app

ChatGPT is useful for scaffold work and boilerplate β€” but always read the output carefully. It occasionally hallucinates Flask APIs that do not exist, or writes code that works but misses edge cases (like the empty-line problem in jokes.txt).

Conclusion

We built a working Flask web app from scratch: routing, templates, static files, forms, and a random joke generator. The project is small, but every concept here β€” @app.route, render_template, url_for, request.form β€” appears in production Flask apps at any scale.

The best way to keep learning is to extend this app. Some ideas: add a /add-joke page that lets you POST new jokes into the file, or store jokes in a SQLite database using Flask-SQLAlchemy. The full source is on GitHub β€” feel free to fork it and experiment.

Did you like this post? Please let me know if you have any comments or suggestions.

Python posts that might be interesting for you


Related tools you may want to try next.

CustomGPT.AI is a very accurate Retrieval-Augmented Generation tool that provides accurate answers using the latest ChatGPT to tackle the AI hallucination problem.

References

  1. Flask Documentation

  2. Flask Quickstart

  3. Flask Routing

  4. The Request Object

  5. Jinja2 Documentation

  6. Flask render_template API

  7. Python venv β€” official docs

  8. Gunicorn β€” Python WSGI HTTP Server

  9. Flask-SQLAlchemy

  10. GitHub: flask-random-joke

desktop bg dark

About Elena

Elena, a PhD in Computer Science, simplifies AI concepts and helps you use machine learning.





Citation
Elena Daehnhardt. (2023) 'Joking Flask App', daehnhardt.com, 10 December 2023. Available at: https://daehnhardt.com/blog/2023/12/10/python-flask-app/
All Posts