Stap 1: CORS in Backend
Je hebt CORS waarschijnlijk al geïnstalleerd. Zo niet:
npm install cors
Open server.js en voeg CORS toe:
import express from 'express';
import cors from 'cors';
import workoutRoutes from './routes/workoutRoutes.js';
const app = express();
// CORS toestaan voor frontend (Live Server poort)
app.use(cors({
origin: 'http://127.0.0.1:5500'
}));
// Middleware voor JSON
app.use(express.json());
// Routes
app.use('/api/workouts', workoutRoutes);
app.use(cors(...))- Geeft toestemming aan frontendorigin: 'http://127.0.0.1:5500'- Dit is de poort van Live Server in VS Code- Dit moet VOOR
app.use(express.json())
Andere poort?
Gebruik je geen Live Server, of draait jouw frontend op een andere poort? Pas dan de origin aan. Tijdens ontwikkeling kun je ook tijdelijk app.use(cors()) gebruiken zonder opties — dan staat alles open.
Belangrijk!
CORS moet bovenaan in server.js, voor alle andere middleware. Herstart backend na deze wijziging.
Stap 2: Frontend aanmaken
Je hebt geen installatie nodig. Maak gewoon een nieuwe map frontend/ naast je backend/ map met drie bestanden:
mijn-project/
├── backend/
│ └── ...
└── frontend/
├── index.html
├── style.css
└── script.js
Maak index.html aan met deze basisstructuur:
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<title>Workouts</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Workouts</h1>
<div id="workout-list"></div>
<script src="script.js"></script>
</body>
</html>
Hoe open je de frontend?
Rechtsklik op index.html in VS Code en kies Open with Live Server. Je frontend draait dan op http://127.0.0.1:5500.
Live Server niet geïnstalleerd? Zoek "Live Server" in de VS Code extensies en installeer hem van Ritwick Dey.
Twee terminals nodig
Terminal 1: Backend starten met npm run dev
Terminal 2: Niet nodig — Live Server regelt de frontend
Project Structuur
Je hebt nu:
mijn-project/
├── backend/
│ ├── controllers/
│ ├── models/
│ ├── routes/
│ ├── server.js
│ └── package.json
│
└── frontend/
├── index.html
├── style.css
└── script.js
Poorten
Backend draait op: http://localhost:4000
Frontend draait op: http://127.0.0.1:5500
Stap 3: Data Ophalen (GET)
Open script.js en voeg het volgende toe:
const API_URL = 'http://localhost:4000/api/workouts';
const fetchWorkouts = async () => {
try {
const response = await fetch(API_URL);
const workouts = await response.json();
renderWorkouts(workouts);
} catch (error) {
console.error('Fout bij ophalen:', error);
}
};
const renderWorkouts = (workouts) => {
const list = document.getElementById('workout-list');
list.innerHTML = '';
if (workouts.length === 0) {
list.innerHTML = '<p>Geen workouts gevonden</p>';
return;
}
workouts.forEach(workout => {
const div = document.createElement('div');
div.innerHTML = `
<h3>${workout.title}</h3>
<p>Reps: ${workout.reps}</p>
<p>Load: ${workout.load} kg</p>
`;
list.appendChild(div);
});
};
// Laad workouts zodra de pagina klaar is
document.addEventListener('DOMContentLoaded', fetchWorkouts);
API_URL- Sla de URL op in een variabele zodat je hem maar één keer hoeft aan te passenconst fetchWorkouts = async () => {}- Arrow function die async isawait fetch(...)- Wacht op response van backendawait response.json()- Wacht tot JSON geparsed istry/catch- Vangt errors oprenderWorkouts()- Aparte arrow function die de HTML opbouwtlist.innerHTML = ''- Leeg de lijst eerst zodat er geen duplicaten komenforEach- Loopt door alle workoutscreateElement+innerHTML- Maakt een HTML-element aan per workoutDOMContentLoaded- Wacht tot de hele HTML geladen is, daarna pasfetchWorkouts()aanroepen
Testen
Open http://127.0.0.1:5500 in de browser (Live Server opent dit automatisch).
Het werkt als:
- Je ziet "Workouts" als titel
- Je ziet workouts uit de database
- Geen CORS errors in console (F12 → Console)
Veelvoorkomende Problemen:
1. CORS error
- Check of de
originin server.js klopt:http://127.0.0.1:5500 - Herstart backend na de wijziging
2. "Geen workouts gevonden"
- Database is leeg — maak eerst workouts aan met Postman
3. Pagina laadt maar toont niks
- Open de browser console (F12) en kijk naar de foutmelding
- Check of backend draait op poort 4000
4. Live Server poort is anders
- Kijk rechtsonder in VS Code — daar zie je de actieve poort
- Pas de
originin server.js aan naar die poort
POST Request — Workout toevoegen
Voeg een formulier toe in index.html:
<form id="add-form">
<input type="text" id="title" placeholder="Titel" required />
<input type="number" id="reps" placeholder="Reps" required />
<input type="number" id="load" placeholder="Load (kg)" required />
<button type="submit">Toevoegen</button>
</form>
En voeg dit toe aan script.js:
document.getElementById('add-form').addEventListener('submit', async (e) => {
e.preventDefault();
const workout = {
title: document.getElementById('title').value,
reps: Number(document.getElementById('reps').value),
load: Number(document.getElementById('load').value)
};
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workout)
});
if (response.ok) {
document.getElementById('add-form').reset();
fetchWorkouts(); // Vernieuw de lijst
} else {
console.error('Fout bij toevoegen');
}
} catch (error) {
console.error('Error:', error);
}
});
addEventListener('submit', ...)- Luistert naar het verzenden van het formuliere.preventDefault()- Voorkomt dat de pagina herlaadt bij submitdocument.getElementById(...).value- Leest de waarde uit een inputveldmethod: 'POST'- Nieuwe data aanmakenheaders- Vertelt backend dat we JSON sturenbody: JSON.stringify(workout)- Zet object om naar JSONform.reset()- Maakt alle velden leeg na succesvol toevoegenfetchWorkouts()- Vernieuwt de lijst zodat de nieuwe workout zichtbaar is
UPDATE Request — Workout aanpassen (PATCH)
Voor een update heb je het ID nodig van de workout. Voeg een verborgen updateformulier toe in index.html:
<div id="update-form-container" style="display:none;">
<h2>Workout aanpassen</h2>
<form id="update-form">
<input type="hidden" id="update-id" />
<input type="text" id="update-title" placeholder="Titel" required />
<input type="number" id="update-reps" placeholder="Reps" required />
<input type="number" id="update-load" placeholder="Load (kg)" required />
<button type="submit">Opslaan</button>
<button type="button" onclick="hideUpdateForm()">Annuleren</button>
</form>
</div>
Pas de renderWorkouts() functie aan zodat elke workout een "Aanpassen" knop krijgt:
workouts.forEach(workout => {
const div = document.createElement('div');
div.innerHTML = `
<h3>${workout.title}</h3>
<p>Reps: ${workout.reps}</p>
<p>Load: ${workout.load} kg</p>
<button onclick="showUpdateForm('${workout._id}', '${workout.title}', ${workout.reps}, ${workout.load})">
Aanpassen
</button>
`;
list.appendChild(div);
});
Voeg deze functies toe aan script.js:
const showUpdateForm = (id, title, reps, load) => {
document.getElementById('update-id').value = id;
document.getElementById('update-title').value = title;
document.getElementById('update-reps').value = reps;
document.getElementById('update-load').value = load;
document.getElementById('update-form-container').style.display = 'block';
};
const hideUpdateForm = () => {
document.getElementById('update-form-container').style.display = 'none';
};
document.getElementById('update-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('update-id').value;
const updatedWorkout = {
title: document.getElementById('update-title').value,
reps: Number(document.getElementById('update-reps').value),
load: Number(document.getElementById('update-load').value)
};
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedWorkout)
});
if (response.ok) {
hideUpdateForm();
fetchWorkouts(); // Vernieuw de lijst
} else {
console.error('Fout bij aanpassen');
}
} catch (error) {
console.error('Error:', error);
}
});
type="hidden"- Onzichtbaar inputveld om het ID op te slaanshowUpdateForm()- Vult het formulier in met de huidige waarden en maakt het zichtbaarhideUpdateForm()- Verbergt het formulier weerstyle.display = 'block'/'none'- Toont of verbergt het formuliermethod: 'PATCH'- UPDATE operatie`${API_URL}/${id}`- URL met het specifieke ID van de workoutfetchWorkouts()- Vernieuwt de lijst na aanpassen
DELETE Request — Workout verwijderen
Voeg een "Verwijderen" knop toe in renderWorkouts() en schrijf de bijbehorende functie:
Pas renderWorkouts() aan:
workouts.forEach(workout => {
const div = document.createElement('div');
div.innerHTML = `
<h3>${workout.title}</h3>
<p>Reps: ${workout.reps}</p>
<p>Load: ${workout.load} kg</p>
<button onclick="showUpdateForm('${workout._id}', '${workout.title}', ${workout.reps}, ${workout.load})">
Aanpassen
</button>
<button onclick="deleteWorkout('${workout._id}')">
Verwijderen
</button>
`;
list.appendChild(div);
});
Voeg de deleteWorkout() functie toe:
const deleteWorkout = async (id) => {
if (!confirm('Weet je zeker dat je deze workout wilt verwijderen?')) return;
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'DELETE'
});
if (response.ok) {
fetchWorkouts(); // Vernieuw de lijst
} else {
console.error('Fout bij verwijderen');
}
} catch (error) {
console.error('Error:', error);
}
};
confirm()- Browser popup voor bevestigingmethod: 'DELETE'- DELETE operatie- Geen
bodynodig — DELETE heeft alleen het ID in de URL fetchWorkouts()- Herlaadt de lijst zodat de verwijderde workout verdwijnttry/catch- Vangt errors op
Volledig Voorbeeld
Hier is de complete index.html en script.js met alle CRUD operaties:
index.html
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<title>Workouts</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Workouts</h1>
<!-- CREATE -->
<h2>Workout toevoegen</h2>
<form id="add-form">
<input type="text" id="title" placeholder="Titel" required />
<input type="number" id="reps" placeholder="Reps" required />
<input type="number" id="load" placeholder="Load (kg)" required />
<button type="submit">Toevoegen</button>
</form>
<!-- UPDATE FORM (verborgen) -->
<div id="update-form-container" style="display:none;">
<h2>Workout aanpassen</h2>
<form id="update-form">
<input type="hidden" id="update-id" />
<input type="text" id="update-title" placeholder="Titel" required />
<input type="number" id="update-reps" placeholder="Reps" required />
<input type="number" id="update-load" placeholder="Load (kg)" required />
<button type="submit">Opslaan</button>
<button type="button" onclick="hideUpdateForm()">Annuleren</button>
</form>
</div>
<!-- READ -->
<h2>Alle workouts</h2>
<div id="workout-list"></div>
<script src="script.js"></script>
</body>
</html>
script.js
const API_URL = 'http://localhost:4000/api/workouts';
// READ - Haal alle workouts op
const fetchWorkouts = async () => {
try {
const response = await fetch(API_URL);
const workouts = await response.json();
renderWorkouts(workouts);
} catch (error) {
console.error('Fout bij ophalen:', error);
}
};
// Toon workouts in de HTML
const renderWorkouts = (workouts) => {
const list = document.getElementById('workout-list');
list.innerHTML = '';
if (workouts.length === 0) {
list.innerHTML = '<p>Geen workouts gevonden</p>';
return;
}
workouts.forEach(workout => {
const div = document.createElement('div');
div.innerHTML = `
<h3>${workout.title}</h3>
<p>Reps: ${workout.reps}</p>
<p>Load: ${workout.load} kg</p>
<button onclick="showUpdateForm('${workout._id}', '${workout.title}', ${workout.reps}, ${workout.load})">
Aanpassen
</button>
<button onclick="deleteWorkout('${workout._id}')">
Verwijderen
</button>
`;
list.appendChild(div);
});
};
// CREATE - Workout toevoegen
document.getElementById('add-form').addEventListener('submit', async (e) => {
e.preventDefault();
const workout = {
title: document.getElementById('title').value,
reps: Number(document.getElementById('reps').value),
load: Number(document.getElementById('load').value)
};
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workout)
});
if (response.ok) {
document.getElementById('add-form').reset();
fetchWorkouts();
}
} catch (error) {
console.error('Error:', error);
}
});
// UPDATE - Formulier tonen met huidige waarden
const showUpdateForm = (id, title, reps, load) => {
document.getElementById('update-id').value = id;
document.getElementById('update-title').value = title;
document.getElementById('update-reps').value = reps;
document.getElementById('update-load').value = load;
document.getElementById('update-form-container').style.display = 'block';
};
const hideUpdateForm = () => {
document.getElementById('update-form-container').style.display = 'none';
};
document.getElementById('update-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('update-id').value;
const updatedWorkout = {
title: document.getElementById('update-title').value,
reps: Number(document.getElementById('update-reps').value),
load: Number(document.getElementById('update-load').value)
};
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedWorkout)
});
if (response.ok) {
hideUpdateForm();
fetchWorkouts();
}
} catch (error) {
console.error('Error:', error);
}
});
// DELETE - Workout verwijderen
const deleteWorkout = async (id) => {
if (!confirm('Weet je zeker dat je deze workout wilt verwijderen?')) return;
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'DELETE'
});
if (response.ok) {
fetchWorkouts();
}
} catch (error) {
console.error('Error:', error);
}
};
// Laad workouts zodra de pagina klaar is
document.addEventListener('DOMContentLoaded', fetchWorkouts);
Dit is een werkende CRUD app!
Je kunt nu:
- ✅ Workouts bekijken (READ)
- ✅ Workouts toevoegen (CREATE / POST)
- ✅ Workouts aanpassen (UPDATE / PATCH)
- ✅ Workouts verwijderen (DELETE)
Samenvatting
Je hebt nu een volledig werkende fullstack app:
Backend (Express):
- MongoDB database verbinding
- Mongoose model en schema
- API routes voor CRUD operaties
- CORS configuratie voor Live Server
Frontend (Vanilla JS):
- HTML pagina geopend via Live Server
- Data ophalen met
fetch() - Data tonen met
forEacheninnerHTML - Formulieren voor toevoegen en aanpassen
Volgende Stappen
Nu CORS en frontend werken, kun je verder met:
- Styling toevoegen in
style.css - Authentication toevoegen aan je backend