Koble frontend og backend med databaser.
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.
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
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)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-corsEnkel CORS (tillat alt - KUN for utvikling):
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # Tillater alle originsSikker 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 responsefrom 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!
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), 200Frontend - 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
| Begrep | Forklaring |
|---|---|
| Fullstack | Både frontend og backend |
| REST API | Tjeneste 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.