Tilbake
6.4
Fullstack-applikasjoner

6.4 Fullstack-applikasjoner

Koble frontend og backend med databaser.

65 min
6 oppgaver
FullstackCRUDFrontend-backendDatabase
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 6 oppgaver

Nå skal vi koble sammen frontend og backend til komplette fullstack-applikasjoner. Vi skal bygge REST API-er med Flask, kommunisere med dem fra JavaScript, og håndtere utfordringer som CORS (Cross-Origin Resource Sharing).

Dette kapittelet gir deg verktøyene til å bygge moderne webapplikasjoner der frontend og backend er separate men kommuniserer sømløst.

REST (Representational State Transfer) er en arkitekturstil for API-er.

Grunnprinsipper:

1. Ressurser identifiseres med URL-er:
- /brukere - samling av brukere
- /brukere/5 - spesifikk bruker
- /brukere/5/innlegg - innlegg fra bruker 5

2. HTTP-metoder indikerer operasjon:
- GET - Hent data (lesing)
- POST - Opprett ny ressurs
- PUT - Oppdater hele ressurs
- PATCH - Oppdater deler av ressurs
- DELETE - Slett ressurs

3. Statuskoder kommuniserer resultat:
- 200 OK - Vellykket
- 201 Created - Ressurs opprettet
- 400 Bad Request - Ugyldig forespørsel
- 404 Not Found - Ressurs ikke funnet
- 500 Internal Server Error - Serverfeil

4. Data utveksles som JSON

✏️Flask REST API
Komplett API for produkter:

from flask import Flask, jsonify, request

app = Flask(__name__)

# "Database" (i virkeligheten ville dette vært en ekte database)
produkter = [
    {"id": 1, "navn": "Laptop", "pris": 8999},
    {"id": 2, "navn": "Mus", "pris": 299},
    {"id": 3, "navn": "Tastatur", "pris": 599}
]

# GET alle produkter
@app.route('/api/produkter', methods=['GET'])
def hent_alle_produkter():
    return jsonify(produkter), 200

# GET ett produkt
@app.route('/api/produkter/<int:id>', methods=['GET'])
def hent_produkt(id):
    produkt = next((p for p in produkter if p['id'] == id), None)
    if produkt is None:
        return jsonify({"error": "Produkt ikke funnet"}), 404
    return jsonify(produkt), 200

# POST nytt produkt
@app.route('/api/produkter', methods=['POST'])
def opprett_produkt():
    data = request.get_json()

    # Validering
    if not data or 'navn' not in data or 'pris' not in data:
        return jsonify({"error": "Navn og pris er påkrevd"}), 400

    # Opprett produkt
    nytt_produkt = {
        "id": max(p['id'] for p in produkter) + 1,
        "navn": data['navn'],
        "pris": data['pris']
    }
    produkter.append(nytt_produkt)

    return jsonify(nytt_produkt), 201

# PUT (oppdater produkt)
@app.route('/api/produkter/<int:id>', methods=['PUT'])
def oppdater_produkt(id):
    produkt = next((p for p in produkter if p['id'] == id), None)
    if produkt is None:
        return jsonify({"error": "Produkt ikke funnet"}), 404

    data = request.get_json()
    produkt['navn'] = data.get('navn', produkt['navn'])
    produkt['pris'] = data.get('pris', produkt['pris'])

    return jsonify(produkt), 200

# DELETE
@app.route('/api/produkter/<int:id>', methods=['DELETE'])
def slett_produkt(id):
    global produkter
    produkter = [p for p in produkter if p['id'] != id]
    return '', 204  # No Content

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

Problemet:
Av sikkerhetsgrunner blokkerer nettlesere forespørsler fra en origin (domene) til en annen. Hvis frontend kjører på localhost:3000 og backend på localhost:5000, vil forespørsler bli blokkert.

Løsningen: CORS
Backend må eksplisitt tillate forespørsler fra frontend.

Installere Flask-CORS:

pip install flask-cors

Enkel CORS (tillat alt - KUN for utvikling):

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # Tillater alle origins

Sikker CORS (produksjon):

CORS(app, origins=[
    "https://mittdomene.no",
    "https://www.mittdomene.no"
])

Manuell CORS:

@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
    response.headers.add('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE')
    return response
✏️Fullstack-eksempel: Frontend + Backend
Backend (Flask API - app.py):
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

oppgaver = []

@app.route('/api/oppgaver', methods=['GET'])
def hent_oppgaver():
    return jsonify(oppgaver)

@app.route('/api/oppgaver', methods=['POST'])
def legg_til_oppgave():
    data = request.get_json()
    ny_oppgave = {
        'id': len(oppgaver) + 1,
        'tekst': data['tekst'],
        'fullfort': False
    }
    oppgaver.append(ny_oppgave)
    return jsonify(ny_oppgave), 201

@app.route('/api/oppgaver/<int:id>', methods=['DELETE'])
def slett_oppgave(id):
    global oppgaver
    oppgaver = [o for o in oppgaver if o['id'] != id]
    return '', 204

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

Frontend (HTML + JavaScript - index.html):

<!DOCTYPE html>
<html>
<head>
    <title>Oppgaveliste</title>
    <style>
        body { font-family: Arial; max-width: 600px; margin: 50px auto; }
        li { margin: 10px 0; }
        button { margin-left: 10px; }
    </style>
</head>
<body>
    <h1>Oppgaveliste</h1>

    <form id="oppgaveSkjema">
        <input type="text" id="oppgaveTekst" placeholder="Ny oppgave" required>
        <button type="submit">Legg til</button>
    </form>

    <ul id="oppgaveListe"></ul>

    <script>
        const API_URL = 'http://localhost:5000/api/oppgaver';
        const skjema = document.getElementById('oppgaveSkjema');
        const liste = document.getElementById('oppgaveListe');

        // Hent og vis oppgaver
        async function hentOppgaver() {
            const response = await fetch(API_URL);
            const oppgaver = await response.json();

            liste.innerHTML = '';
            oppgaver.forEach(oppgave => {
                const li = document.createElement('li');
                li.innerHTML = `
                    ${oppgave.tekst}
                    <button onclick="slettOppgave(${oppgave.id})">Slett</button>
                `;
                liste.appendChild(li);
            });
        }

        // Legg til oppgave
        skjema.addEventListener('submit', async (e) => {
            e.preventDefault();
            const tekst = document.getElementById('oppgaveTekst').value;

            await fetch(API_URL, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ tekst })
            });

            document.getElementById('oppgaveTekst').value = '';
            hentOppgaver();
        });

        // Slett oppgave
        async function slettOppgave(id) {
            await fetch(`${API_URL}/${id}`, { method: 'DELETE' });
            hentOppgaver();
        }

        // Last inn oppgaver ved sideinnlasting
        hentOppgaver();
    </script>
</body>
</html>

Dette er en komplett fullstack-applikasjon med separate frontend og backend!

✏️Feilhåndtering i fullstack
Backend - Robuste feilmeldinger:
from flask import Flask, jsonify, request

@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Ressurs ikke funnet"}), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({"error": "Intern serverfeil"}), 500

@app.route('/api/bruker/<int:id>', methods=['GET'])
def hent_bruker(id):
    bruker = finn_bruker(id)  # Fiktiv funksjon

    if not bruker:
        return jsonify({"error": "Bruker ikke funnet"}), 404

    return jsonify(bruker), 200

Frontend - Håndtere API-feil:

async function hentBruker(id) {
    try {
        const response = await fetch(`/api/bruker/${id}`);

        if (!response.ok) {
            // Håndter HTTP-feil (4xx, 5xx)
            const error = await response.json();
            throw new Error(error.error || 'Noe gikk galt');
        }

        const bruker = await response.json();
        console.log('Bruker:', bruker);
        return bruker;

    } catch (error) {
        console.error('Feil:', error.message);

        // Vis feilmelding til bruker
        document.getElementById('feilmelding').textContent =
            'Kunne ikke hente bruker: ' + error.message;
    }
}

Beste praksis:
- Backend returnerer klare feilmeldinger som JSON
- Frontend sjekker response.ok før parsing
- Brukervennlige feilmeldinger i UI

Nå skal du løse oppgaver om fullstack-utvikling, REST API-er og CORS.

Oppsummering

I dette kapittelet har du lært:

- Fullstack: kombinasjon av frontend og backend.
- Flask REST API: serveren leverer data.
- Frontend + backend: samhandling i en komplett app.
- Feilhaandtering: robust oppførsel i hele stacken.

Noekkelbegreper


BegrepForklaring
FullstackBåde frontend og backend
REST APITjeneste som leverer data over HTTP

Oppgaver

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.