Project A – Bouw een Data Dashboard met Flask

01 - Inleiding: doel van Project A

In dit project bouwen we een eenvoudige Flask-website met een dashboard dat gegevens toont uit een bestaande SQLite-database. Deze database wordt gebruikt tijdens de SQL-cursus en wordt in dit project gebruikt als vaste databron. We gaan de database dus niet aanpassen of opnieuw opzetten, maar richten ons volledig op het uitlezen van data en het presenteren ervan in een webapplicatie.

Op het dashboard worden drie grafieken weergegeven die inzicht geven in de gegevens uit de database. Hiermee leer je hoe Flask samenwerkt met SQLite, hoe queryresultaten in Python worden verwerkt en hoe deze data visueel wordt gepresenteerd in een HTML-pagina. De nadruk ligt op een heldere structuur en begrijpelijke code, zodat dit project een logisch en toegankelijk eerste Flask-project vormt.

Bekijk het eindresultaat

02 - Een virtual environment (venv) opzetten
  • Flask is een module (package) die je kunt toevoegen aan Python.
  • Vanaf dit punt werken we altijd binnen een virtual environment voordat we Flask of andere packages installeren.
  • Een virtual environment (venv) is een afgesloten Python-omgeving die speciaal voor één project wordt gebruikt.
  • Alle Python-pakketten die je in een venv installeert, zijn alleen beschikbaar binnen dat project.
  • Dit voorkomt conflicten tussen projecten die verschillende libraries of versies nodig hebben.
  • We installeren Flask en andere packages zonder de globale Python-installatie te beïnvloeden.
  • Het project werkt hetzelfde op Windows en macOS.
  • De projectomgeving is later eenvoudig opnieuw op te zetten of te delen.
  • Controleer eerst of Python is geïnstalleerd en bereikbaar via de terminal of opdrachtprompt.
  • Gebruik hiervoor het volgende commando:
python --version
  • Kies een locatie op je computer waar je Python-projecten wilt opslaan.
  • Maak daar een nieuwe map aan voor dit project, bijvoorbeeld project_a.
  • Open een terminal (macOS) of opdrachtprompt (Windows).
  • Navigeer naar deze map en zorg dat je er in staat.
python -m venv venv
  • De map venv wordt nu aangemaakt in je project.
  • Deze map bevat een eigen Python-interpreter en package-omgeving.
  • Activeer de virtual environment (voor Windows) met:
venv\Scripts\activate
  • Activeer de virtual environment (voor Linux / Mac) met:
source venv/bin/activate
  • Na activatie zie je de naam van de venv, meestal (venv), aan het begin van de prompt.
  • Alle Python- en pip-commando’s die je nu uitvoert, horen bij deze virtual environment.
  • Je kunt de virtual environment op elk moment verlaten met:
deactivate
  • Activeer altijd eerst de virtual environment voordat je Flask of andere Python-packages installeert.
  • De map venv hoort bij het project en wordt meestal niet gedeeld via versiebeheer.
  • In de volgende stap installeren we Flask binnen deze virtual environment.
03 - Flask installeren met pip
  • Nu de virtual environment actief is, kunnen we Flask installeren.
  • Hiervoor gebruiken we pip, de standaard package manager van Python.
  • Met pip installeer je extra modules en libraries die niet standaard in Python zitten.
  • In dit project gebruiken we Flask om een webapplicatie te bouwen.
  • Omdat de virtual environment actief is, wordt Flask alleen in dit project geïnstalleerd.
  • Controleer eerst of je prompt nog steeds laat zien dat de venv actief is, bijvoorbeeld met (venv) vooraan.
  • Installeer daarna Flask met het volgende commando:
pip install flask
  • Python downloadt nu Flask en de benodigde onderdelen.
  • Je ziet tijdens de installatie regels verschijnen met packages die worden opgehaald en geïnstalleerd.
  • Na de installatie kun je controleren of Flask correct aanwezig is.
  • Gebruik daarvoor bijvoorbeeld:
pip show flask
  • Je krijgt dan informatie te zien zoals de naam van het package, de versie en de installatielocatie.
  • Staat Flask in de uitvoer, dan is de installatie gelukt.
  • Je kunt ook alle geïnstalleerde packages in de virtual environment bekijken met:
pip list
  • In de lijst hoort nu ook Flask te staan.
  • Mogelijk zie je daarnaast extra packages die Flask nodig heeft om te werken.
  • Installeer Flask altijd nadat je de virtual environment hebt geactiveerd.
  • Doe je dat niet, dan komt Flask mogelijk in je globale Python-omgeving terecht in plaats van in dit project.
  • Dat maakt projecten lastiger te beheren en kan later voor verwarring zorgen.
  • In de volgende stap maken we een minimale Flask-projectstructuur en zetten we de eerste bestanden klaar.
04 - Een minimale Flask-projectstructuur
  • Nu Flask is geïnstalleerd, kunnen we de basis van het project gaan opzetten.
  • In dit project werken we met een bestaande SQLite-database die de basis vormt voor het dashboard.
  • Daarom richten we de projectmap meteen zo in dat code, templates, opmaak en database logisch van elkaar gescheiden zijn.
  • Maak in je projectmap in elk geval de volgende onderdelen aan:
project_a/
│
├── app.py
├── data/
│   └── tysql.sqlite
├── templates/
│   └── index.html
└── static/
    └── style.css
  • In dit project krijgt elk onderdeel een eigen taak.
  • Daardoor blijft de applicatie duidelijk opgebouwd.
  • app.py bevat de Flask-app en de routes van de website.
  • In de map data plaatsen we de bestaande SQLite-database die we gaan uitlezen.
  • In de map templates zet Flask de HTML-bestanden.
  • In de map static komen bestanden zoals CSS, afbeeldingen en later eventueel JavaScript.
  • Maak eerst de mappen data, templates en static aan.
  • Maak daarna ook de bestanden app.py, index.html en style.css.
  • Voor dit project hoef je de database dus niet zelf te maken.
  • Je kunt de database direct downloaden via deze link: Download de SQLite-database (tysql.sqlite)
  • Sla dit bestand na het downloaden op in je eigen projectmap in de map data.
  • Het pad wordt dan: project_a/data/tysql.sqlite
  • In app.py zetten we eerst een minimale Flask-app klaar:
from flask import Flask, render_template

app = Flask(__name__)


@app.route("/")
def index():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)
  • Met Flask(__name__) maak je een nieuwe Flask-app aan.
  • De route / is de startpagina van de website.
  • Met render_template("index.html") laat Flask een HTML-bestand uit de map templates zien.
  • Met debug=True krijg je tijdens het ontwikkelen duidelijke foutmeldingen en wordt de app automatisch herladen na wijzigingen.
  • Zet vervolgens een eenvoudige basis in templates/index.html:
<!doctype html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>Mijn Flask Dashboard</h1>
    <p>De basis van de webapp werkt.</p>
</body>
</html>
  • Flask gebruikt url_for(...) om automatisch de juiste link naar bestanden in de map static te maken.
  • Dat is veiliger en handiger dan zelf relatieve paden typen.
  • Zet in static/style.css bijvoorbeeld:
body {
    font-family: Arial, sans-serif;
    margin: 40px;
    background-color: #f4f4f4;
}

h1 {
    color: #1f3c88;
}
  • Met deze structuur heb je een werkende basis waarop we verder kunnen bouwen.
  • De database staat nu al klaar in de map data, zodat we die in de volgende stap kunnen koppelen aan de Flask-app.
05 - Werken met een bestaande SQLite-database
  • SQLite is een lichte database die gewoon in één bestand staat.
  • In ons project is dat bestand: data/tysql.sqlite
  • Python heeft standaard al ondersteuning voor SQLite via de module sqlite3.
  • Je hoeft hiervoor dus geen extra package met pip te installeren.
  • In app.py importeren we eerst de module sqlite3:
import sqlite3
from flask import Flask, render_template
  • Daarna maken we hiervoor meteen een aparte functie.
def get_db_connection():
    connection = sqlite3.connect("data/tysql.sqlite")
    connection.row_factory = sqlite3.Row
    return connection
  • Deze functie opent het databasebestand in de map data.
  • Elke keer dat we data nodig hebben, kunnen we deze functie opnieuw gebruiken.
  • Met sqlite3.Row kunnen we straks waarden uit een rij ophalen met kolomnamen in plaats van alleen met indexnummers.
  • Plaats deze functie in app.py direct onder de regel app = Flask(__name__).
  • Zo staat de functie boven de routes en is hij overal in het bestand beschikbaar.
  • Het is nog steeds belangrijk om de verbinding te sluiten zodra je klaar bent met de database.
  • Dat doe je bijvoorbeeld zo:
connection = get_db_connection()
connection.close()
  • Als je dit uitvoert zonder foutmelding, weet je dat Python het databasebestand kan vinden.
  • Krijg je een fout, controleer dan vooral of het bestand echt in de map data staat.
  • De code in app.py ziet er tot nu toe zo uit:
import sqlite3
from flask import Flask, render_template

app = Flask(__name__)


def get_db_connection():
    connection = sqlite3.connect("data/tysql.sqlite")
    connection.row_factory = sqlite3.Row
    return connection


@app.route("/")
def index():
    connection = get_db_connection()
    connection.close()
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)
  • In de volgende stap gaan we SQL-queries uitvoeren om gegevens uit de database op te halen voor ons dashboard.
06 - Data ophalen met SQL-queries
  • In dit project gebruiken we in Python rechtstreeks SQL-queries om data uit de SQLite-database op te halen.
  • De resultaten halen we daarna op en geven we door aan de HTML-template.
  • In de route index() kunnen we nu de verbinding gebruiken die we in stap 5 hebben voorbereid.
  • Daarna voeren we een SQL-query uit met een cursor.
@app.route("/")
def index():
    connection = get_db_connection()
    cursor = connection.cursor()

    cursor.execute("""
        SELECT order_num, SUM(quantity * item_price) AS order_total
        FROM OrderItems
        GROUP BY order_num
    """)

    orders = cursor.fetchall()
    connection.close()
    return render_template("index.html", orders=orders)
  • Met connection.cursor() maken we een cursor aan die SQL-commando's kan uitvoeren.
  • Met cursor.execute(...) voeren we de query uit.
  • Met cursor.fetchall() halen we alle gevonden rijen op.
  • De variabele orders bevat daarna alle resultaten van de query.
  • Omdat we in stap 5 sqlite3.Row hebben ingesteld, kunnen we straks in de template werken met kolomnamen.
  • Als je deze query gebruikt, moet de code in app.py er nu zo uitzien:
import sqlite3
from flask import Flask, render_template

app = Flask(__name__)


def get_db_connection():
    connection = sqlite3.connect("data/tysql.sqlite")
    connection.row_factory = sqlite3.Row
    return connection


@app.route("/")
def index():
    connection = get_db_connection()
    cursor = connection.cursor()

    cursor.execute("""
        SELECT order_num, SUM(quantity * item_price) AS order_total
        FROM OrderItems
        GROUP BY order_num
    """)

    orders = cursor.fetchall()
    connection.close()

    return render_template("index.html", orders=orders)


if __name__ == "__main__":
    app.run(debug=True)
  • In de volgende stap gaan we bekijken hoe we deze data zichtbaar kunnen maken in de HTML-template.
07 - Flask routes en views
  • In Flask werkt een website met routes en view-functies.
  • Een route bepaalt welke URL bij welke functie hoort.
  • In ons project gebruiken we deze route:
@app.route("/")
def index():
    connection = get_db_connection()
    cursor = connection.cursor()

    cursor.execute("""
        SELECT order_num, SUM(quantity * item_price) AS order_total
        FROM OrderItems
        GROUP BY order_num
    """)

    orders = cursor.fetchall()
    connection.close()

    return render_template("index.html", orders=orders)
  • De route / is de homepage van de website.
  • Als iemand die pagina bezoekt, voert Flask de functie index() uit.
  • Die functie noemen we een view-functie.
  • Een view-functie haalt data op en bepaalt wat er uiteindelijk aan de bezoeker wordt getoond.
  • In dit voorbeeld gebeurt er drie dingen in de functie index():
  • Er wordt een verbinding met de database gemaakt.
  • Er wordt data opgehaald met een SQL-query.
  • Daarna wordt de template index.html getoond.
  • Met deze regel geven we de opgehaalde data door aan de template:
return render_template("index.html", orders=orders)
  • De template krijgt daarmee toegang tot de variabele orders.
  • In de HTML kunnen we die data daarna gebruiken om inhoud op het scherm te zetten.
  • De naam index() is gewoon een functienaam in Python.
  • De URL wordt bepaald door @app.route("/"), niet door de functienaam zelf.
  • In de volgende stap gaan we de data uit orders zichtbaar maken in templates/index.html.
08 - Templates: data tonen in HTML
  • De query in app.py haalt data op uit de database en geeft die door aan de template als orders.
  • In templates/index.html kunnen we die data nu zichtbaar maken.
  • Flask gebruikt hiervoor de template-engine Jinja.
  • Daarmee kun je in een HTML-bestand werken met Python-achtige variabelen en lussen.
  • Een eenvoudige versie van templates/index.html kan er dan zo uitzien:
<!doctype html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>Order Dashboard</h1>

    <table>
        <thead>
            <tr>
                <th>Ordernummer</th>
                <th>Ordertotaal</th>
            </tr>
        </thead>
        <tbody>
            {% for order in orders %}
                <tr>
                    <td>{{ order["order_num"] }}</td>
                    <td>{{ order["order_total"] }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
</body>
</html>
  • Met {% for order in orders %} lopen we door alle resultaten uit de query.
  • Elke order is dan één rij uit de database.
  • Met {{ order["order_num"] }} tonen we het ordernummer in de eerste kolom.
  • Met {{ order["order_total"] }} tonen we het totaalbedrag van die order in de tweede kolom.
  • Dat werkt omdat we in app.py deze regel hebben toegevoegd:
connection.row_factory = sqlite3.Row
  • Daardoor kunnen we in de template werken met kolomnamen zoals order_num en order_total.
  • Zo zie je direct dat Flask, SQLite en het HTML-template correct met elkaar samenwerken.
  • In de volgende stap gaan we deze data niet alleen als tabel tonen, maar gebruiken als basis voor grafieken in het dashboard.
09 - Grafieken maken voor het dashboard
  • In deze stap maken we een eerste eenvoudige grafiek op basis van de ordergegevens.
  • We gebruiken hiervoor dezelfde query als in de vorige stappen.
  • De route in app.py kan dus gewoon hetzelfde blijven:
@app.route("/")
def index():
    connection = get_db_connection()
    cursor = connection.cursor()

    cursor.execute("""
        SELECT order_num, SUM(quantity * item_price) AS order_total
        FROM OrderItems
        GROUP BY order_num
    """)
    orders = cursor.fetchall()

    connection.close()

    return render_template("index.html", orders=orders)
  • Voor de grafiek gebruiken we de ordernummers als labels en de ordertotalen als waarden.
  • In het template kunnen we nu een grafiek toevoegen met Chart.js.
  • Voeg in templates/index.html een html-canvas toe voor de grafiek:
<h2>Ordertotalen per order</h2>
<canvas id="ordersChart"></canvas>
  • Onder in dezelfde template - net voor het sluiten van de </body> kun je vervolgens deze scriptcode plaatsen:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
    const ordersChart = document.getElementById("ordersChart");

    new Chart(ordersChart, {
        type: "bar",
        data: {
            labels: [
                {% for order in orders %}
                    "Order {{ order['order_num'] }}"{% if not loop.last %}, {% endif %}
                {% endfor %}
            ],
            datasets: [{
                label: "Ordertotaal",
                data: [
                    {% for order in orders %}
                        {{ order["order_total"] }}{% if not loop.last %}, {% endif %}
                    {% endfor %}
                ],
                backgroundColor: "#1f3c88"
            }]
        },
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true
                }
            }
        }
    });
</script>
  • De gegevens voor de grafiek komen rechtstreeks uit dezelfde query in app.py.
  • In de template gebruiken we dus opnieuw kolomnamen zoals order_num en order_total.
  • Als alles goed werkt, zie je nu bovenaan een tabel met ordertotalen en daaronder een eenvoudige staafgrafiek.
  • Later kun je dit dashboard uitbreiden met extra queries en grafieken die inhoudelijk meer inzicht geven.
  • In de laatste stap ronden we het project af en kijken we kort vooruit naar mogelijke uitbreidingen.
10 - Project afronden en vooruitblik
  • In deze laatste stap maken we het dashboard rustiger en strakker.
  • We laten in het eindresultaat alleen de grafiek zien en halen de tabel uit het template weg.
  • We geven de pagina een donkere achtergrond met kleur #262626.
  • Ook centreren we de grafiek horizontaal op de pagina.
  • De CSS houden we daarbij zo minimalistisch mogelijk.
  • Pas eerst templates/index.html aan zodat alleen de titel, de grafiek en het script overblijven:
<!doctype html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TySQL Data Dashboard</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>TySQL Data Dashboard</h1>
    <h2>Ordertotalen per order</h2>

    <div class="chart-wrapper">
        <canvas id="ordersChart"></canvas>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
        const ordersChart = document.getElementById("ordersChart");

        new Chart(ordersChart, {
            type: "bar",
            data: {
                labels: [
                    {% for order in orders %}
                        "Order {{ order['order_num'] }}"{% if not loop.last %}, {% endif %}
                    {% endfor %}
                ],
                datasets: [{
                    label: "Ordertotaal",
                    data: [
                        {% for order in orders %}
                            {{ order["order_total"] }}{% if not loop.last %}, {% endif %}
                        {% endfor %}
                    ],
                    backgroundColor: "#4f7df0"
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: {
                        beginAtZero: true
                    }
                }
            }
        });
    </script>
</body>
</html>
  • Daarna kan static/style.css er veel eenvoudiger zo uitzien:
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 40px 20px;
    background-color: #262626;
    color: #f5f5f5;
}

h1,
h2 {
    text-align: center;
}

.chart-wrapper {
    max-width: 700px;
    margin: 0 auto;
}
  • Met deze laatste aanpassingen heeft het project een netter en duidelijker eindresultaat.
  • Vanaf hier kun je het dashboard uitbreiden met nieuwe queries, extra tabellen en meer grafieken.

Bekijk het eindresultaat in een nieuwe tab