Tilbake

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

Frontend

Presentasjonslaget

HTML-siden brukeren ser, med skjema for alias og melding.

Backend

Logikklaget

Flask tar imot forespørsler, kjører Python-kode og sender svar tilbake.

Database

Datalaget

SQLite lagrer meldingene i filen vegg.db på servermaskinen.

LAN

Nettverkslaget

Klientene kobler til serverens lokale IP-adresse på port 5000.

Systemet dere bygger

Dette er en åpen lab-app uten innlogging. Bruk alias eller gruppenavn, og ikke skriv sensitive eller private opplysninger i meldingsfeltet.

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.

Klient

Elevmaskin

Nettleseren åpner http://SERVER-IP:5000 og sender skjema.

LAN + HTTP
Backend

Flask på servermaskinen

Validerer input, kjører Python og sender ferdig HTML tilbake.

SQL
Database

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

  1. Sett opp en ruter som et lukket lokalt nettverk uten internett. Eksempel på SSID: IT-Lab-Nett.
  2. Koble servermaskinen og klientmaskinene til samme nettverk.
  3. Finn IPv4-adressen til servermaskinen.
Hvis labnettverket ikke har internett, må Flask installeres før dere kobler servermaskinen til det lukkede nettverket. Se Fase 4 for kommandoene.
ipconfig getifaddr en0
ipconfig getifaddr en1
ifconfig | grep "inet "
Bruk IP-adressen som hører til Wi-Fi-/LAN-nettet dere er koblet til. Hvis servermaskinen får 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)
Lab-notat: 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.

Siden denne laben kan kjøres på et lukket LAN uten internett, bruker vi intern CSS i stedet for Bootstrap-CDN. Da fungerer designet selv om nettverket ikke har tilgang til eksterne filer.
<!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

  1. Lag et virtuelt Python-miljø og installer Flask der. Gjør dette mens maskinen har internettilgang.
  2. Start serveren fra samme mappe som app.py.
  3. 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
Husk: Hver nye prosjektmappe med egen .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.

SQLite fungerer fint for små lokale demoer. Hvis mange sender samtidig og du får 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 }}.