Flask 2.1
Tredelt arkitektur over lokalt nettverk (LAN)
I denne modulen bygger dere en liten meldingsvegg der flere maskiner i klasserommet kan sende data til samme Flask-server. Målet er å se hvordan frontend, backend, database og nettverk henger sammen i ett system.
Hva du skal lære
Presentasjonslaget
HTML-siden brukeren ser, med skjema for alias og melding.
Logikklaget
Flask tar imot forespørsler, kjører Python-kode og sender svar tilbake.
Datalaget
SQLite lagrer meldingene i filen vegg.db på servermaskinen.
Nettverkslaget
Klientene kobler til serverens lokale IP-adresse på port
5000.
Systemet dere bygger
Dette er den samme grunnflyten som Supabase vanligvis håndterer for dere. I stedet for at Supabase tar seg av server, API og databasekobling i bakgrunnen, bygger dere selv en liten server som andre maskiner på LAN kan sende HTTP-forespørsler til.
Elevmaskin
Nettleseren åpner http://SERVER-IP:5000 og sender skjema.
Flask på servermaskinen
Validerer input, kjører Python og sender ferdig HTML tilbake.
SQLite-fil
Meldingene lagres i vegg.db på servermaskinen.
1. Nettleser
Andre i klassen åpner http://SERVER-IP:5000, fyller ut skjema og
sender en POST-request.
2. Flask-server
Python-koden validerer meldingen, skriver til databasen og renderer siden på nytt.
3. SQLite
Alle meldinger lagres lokalt i en tabell og hentes ut i synkende rekkefølge.
Fase 1 - Sett opp lokalt nettverk
- Sett opp en ruter som et lukket lokalt nettverk uten internett. Eksempel på SSID:
IT-Lab-Nett. - Koble servermaskinen og klientmaskinene til samme nettverk.
- Finn IPv4-adressen til servermaskinen.
ipconfig getifaddr en0
ipconfig getifaddr en1
ifconfig | grep "inet "
192.168.1.14, skal klientene åpne http://192.168.1.14:5000 i nettleseren.
Andre maskiner skal ikke bruke 127.0.0.1, fordi det alltid betyr "min egen maskin".
Fase 2 - Lag Flask-backend
Opprett filen app.py i prosjektmappen.
import os
import sqlite3
from datetime import datetime
from flask import Flask, render_template, request, redirect
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(BASE_DIR, "vegg.db")
app = Flask(__name__)
def init_db():
with sqlite3.connect(DB_PATH, timeout=10) as conn:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS meldinger (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT DEFAULT 'Anonym',
tekst TEXT NOT NULL,
timestamp TEXT NOT NULL
)
""")
conn.commit()
init_db()
@app.route("/")
def index():
with sqlite3.connect(DB_PATH, timeout=10) as conn:
cursor = conn.cursor()
cursor.execute("SELECT alias, tekst, timestamp FROM meldinger ORDER BY id DESC")
alle_meldinger = cursor.fetchall()
return render_template("index.html", meldinger=alle_meldinger)
@app.route("/send", methods=["POST"])
def send_melding():
bruker_alias = (request.form.get("alias") or "").strip() or "Anonym"
melding_tekst = (request.form.get("tekst") or "").strip()
if len(bruker_alias) > 30:
return "Alias kan maks være 30 tegn.", 400
if not melding_tekst:
return "Meldingen kan ikke være tom.", 400
if len(melding_tekst) > 240:
return "Meldingen kan maks være 240 tegn.", 400
naa_tid = datetime.now().strftime("%H:%M:%S")
with sqlite3.connect(DB_PATH, timeout=10) as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO meldinger (alias, tekst, timestamp) VALUES (?, ?, ?)",
(bruker_alias, melding_tekst, naa_tid)
)
conn.commit()
return redirect("/")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
host="0.0.0.0" gjør at andre maskiner på LAN kan koble til. Bruk dette
bare på lukket labnettverk. Ikke kjør Flask sin debug-server åpent på internett.
Fase 3 - Lag frontend med Jinja2
Lag mappen templates, og opprett filen templates/index.html.
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Statusvegg - Tredelt Arkitektur</title>
<style>
body {
margin: 0;
background: #f5f7fb;
color: #172033;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
max-width: 860px;
margin: 0 auto;
padding: 32px 16px;
}
header,
form,
article,
.empty-state {
background: #fff;
border: 1px solid #e3e8f0;
border-radius: 10px;
box-shadow: 0 8px 22px rgba(20, 31, 53, 0.06);
}
header {
padding: 24px;
margin-bottom: 18px;
}
h1,
h2,
p {
margin-top: 0;
}
form {
display: grid;
gap: 12px;
padding: 18px;
margin-bottom: 24px;
}
label {
font-weight: 700;
}
input,
textarea,
button {
width: 100%;
box-sizing: border-box;
border-radius: 8px;
font: inherit;
}
input,
textarea {
border: 1px solid #cbd5e1;
padding: 10px 12px;
}
button {
border: 0;
background: #0d6efd;
color: #fff;
padding: 11px 14px;
font-weight: 700;
cursor: pointer;
}
.message-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
}
article,
.empty-state {
padding: 16px;
}
.message-meta {
display: flex;
justify-content: space-between;
gap: 12px;
color: #64748b;
font-size: 0.9rem;
margin-bottom: 8px;
}
</style>
</head>
<body>
<main>
<header>
<p>LAN-status</p>
<h1>Lokalt Statusdashboard</h1>
<p>Meldinger lagres i SQLite på Flask-serveren.</p>
</header>
<section>
<form action="/send" method="POST">
<div>
<label for="alias">Alias</label>
<input id="alias" type="text" name="alias" maxlength="30" placeholder="F.eks. Gruppe 3">
</div>
<div>
<label for="tekst">Melding</label>
<textarea id="tekst" name="tekst" rows="3" maxlength="240" required placeholder="Skriv en kort status..."></textarea>
</div>
<button type="submit">Send til server</button>
</form>
</section>
<section>
<h2>Logg fra database</h2>
<div class="message-grid">
{% if meldinger %}
{% for alias, tekst, timestamp in meldinger %}
<article>
<div class="message-meta">
<strong>{{ alias }}</strong>
<span>{{ timestamp }}</span>
</div>
<p>{{ tekst }}</p>
</article>
{% endfor %}
{% else %}
<div class="empty-state">
<p>Databasetabellen er tom.</p>
</div>
{% endif %}
</div>
</section>
</main>
</body>
</html>
Fase 4 - Kjør og test
- Lag et virtuelt Python-miljø og installer Flask der. Gjør dette mens maskinen har internettilgang.
- Start serveren fra samme mappe som
app.py. - Test først på servermaskinen, deretter fra en annen maskin på samme LAN.
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install flask
python3 app.py
.venv starter med et tomt Python-miljø.
Da må pakkene installeres på nytt i den mappen, for eksempel med python3 -m pip install flask.
Når serveren kjører, skal Flask vise at den lytter på 0.0.0.0:5000. Andre i klassen bruker serverens
lokale IP-adresse i nettleseren.
database is locked, prøv igjen etter et øyeblikk. I større flerbrukersystemer bruker man ofte
en database-server, for eksempel PostgreSQL.
Feilsøking på macOS
| Problem | Forklaring | Løsning |
|---|---|---|
| Connection refused | Brannmuren blokkerer innkommende trafikk til port 5000. |
Tillat innkommende tilkoblinger for Python, eller deaktiver brannmuren midlertidig under laben. |
| Nettleseren prøver HTTPS | Flask-laben kjører vanlig HTTP, ikke HTTPS. | Skriv hele adressen med http://, for eksempel http://192.168.1.14:5000. |
| Feil IP eller manglende port | Klienten må treffe riktig maskin og riktig port. | Sjekk IP med ipconfig getifaddr en0, prøv en1 hvis den er tom, og husk
:5000. |
| Ustylet side | Lukket LAN har ofte ikke internett, så CDN-er laster ikke. | Bruk intern CSS i templates/index.html, slik eksempelet over gjør. |
Ekstraoppgave
Be Codex redesigne templates/index.html uten å endre Flask-koblingen. Kravet er at
form action="/send" method="POST", feltnavnene alias og tekst, og
Jinja2-løkken
med meldinger må bevares.
Du er en erfaren frontend-utvikler. Redesign templates/index.html for en Flask-app som viser en lokal statusvegg.
Bevar funksjonaliteten: form action="/send" method="POST", name="alias", name="tekst", required på tekstfeltet,
{% if meldinger %}, {% for alias, tekst, timestamp in meldinger %}, {{ alias }}, {{ timestamp }} og {{ tekst }}.