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);
Wat gebeurt hier?
  • app.use(cors(...)) - Geeft toestemming aan frontend
  • origin: '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);
Wat gebeurt hier?
  • API_URL - Sla de URL op in een variabele zodat je hem maar één keer hoeft aan te passen
  • const fetchWorkouts = async () => {} - Arrow function die async is
  • await fetch(...) - Wacht op response van backend
  • await response.json() - Wacht tot JSON geparsed is
  • try/catch - Vangt errors op
  • renderWorkouts() - Aparte arrow function die de HTML opbouwt
  • list.innerHTML = '' - Leeg de lijst eerst zodat er geen duplicaten komen
  • forEach - Loopt door alle workouts
  • createElement + innerHTML - Maakt een HTML-element aan per workout
  • DOMContentLoaded - Wacht tot de hele HTML geladen is, daarna pas fetchWorkouts() 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 origin in 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 origin in 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);
  }
});
Wat gebeurt hier?
  • addEventListener('submit', ...) - Luistert naar het verzenden van het formulier
  • e.preventDefault() - Voorkomt dat de pagina herlaadt bij submit
  • document.getElementById(...).value - Leest de waarde uit een inputveld
  • method: 'POST' - Nieuwe data aanmaken
  • headers - Vertelt backend dat we JSON sturen
  • body: JSON.stringify(workout) - Zet object om naar JSON
  • form.reset() - Maakt alle velden leeg na succesvol toevoegen
  • fetchWorkouts() - 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);
  }
});
Wat gebeurt hier?
  • type="hidden" - Onzichtbaar inputveld om het ID op te slaan
  • showUpdateForm() - Vult het formulier in met de huidige waarden en maakt het zichtbaar
  • hideUpdateForm() - Verbergt het formulier weer
  • style.display = 'block' / 'none' - Toont of verbergt het formulier
  • method: 'PATCH' - UPDATE operatie
  • `${API_URL}/${id}` - URL met het specifieke ID van de workout
  • fetchWorkouts() - 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);
  }
};
Wat gebeurt hier?
  • confirm() - Browser popup voor bevestiging
  • method: 'DELETE' - DELETE operatie
  • Geen body nodig — DELETE heeft alleen het ID in de URL
  • fetchWorkouts() - Herlaadt de lijst zodat de verwijderde workout verdwijnt
  • try/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 forEach en innerHTML
  • 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