Wat is het Probleem?
Je hebt werkende backend (Express API) op http://localhost:4000. Je wilt frontend (React app)
maken op http://localhost:5173.
Als frontend data ophaalt van backend, krijg je deze error:
CORS Error:
Access to fetch at 'http://localhost:4000/api/workouts' from origin
'http://localhost:5173' has been blocked by CORS policy
Wat is CORS?
CORS = Cross-Origin Resource Sharing
Probleem: Backend (poort 4000) en frontend (poort 5173) draaien op verschillende poorten. Dat zijn technisch verschillende "origins".
Oplossing: Backend moet expliciet toestemming geven aan frontend. Dat doe je met cors package.
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
app.use(cors({
origin: 'http://localhost:5173'
}));
// Middleware voor JSON
app.use(express.json());
// Routes
app.use('/api/workouts', workoutRoutes);
app.use(cors(...))- Geeft toestemming aan frontendorigin: 'http://localhost:5173'- Staat alleen deze URL toe- Dit moet VOOR
app.use(express.json())
Belangrijk!
CORS moet bovenaan in server.js, voor alle andere middleware. Herstart backend na deze wijziging.
Stap 2: React Frontend met Vite
We gebruiken Vite omdat het:
- Super snel is
- Makkelijk op te zetten
- Hot reload heeft (wijzigingen direct zien)
Maak nieuw React project
Open NIEUWE terminal (laat backend draaien!). Ga naar hoofdfolder waar backend/ in staat.
# Als je in backend/ zit, ga omhoog
cd ..
# Check waar je bent - je zou 'backend' moeten zien
ls
# Maak Vite project
npm create vite@latest
Terminal blijft hangen?
Als terminal vraagt om packages te installeren, druk y en Enter.
Vite stelt vragen. Vul in:
Invullen:
- Project name:
frontend - Framework:
React(met pijltjes) - Variant:
JavaScript
# Ga in frontend folder
cd frontend
# Installeer packages
npm install
# Start frontend
npm run dev
Het werkt!
Je ziet: Local: http://localhost:5173/
Open deze URL in browser. Je ziet werkende React app.
Project Structuur
Je hebt nu:
mijn-project/
├── backend/
│ ├── controllers/
│ ├── models/
│ ├── routes/
│ ├── server.js
│ └── package.json
│
└── frontend/
├── src/
│ ├── App.jsx
│ └── main.jsx
├── index.html
└── package.json
Twee terminals
Terminal 1: Backend op poort 4000
Terminal 2: Frontend op poort 5173
Stap 3: Data Ophalen in React
Open frontend/src/App.jsx en vervang volledige inhoud:
import { useEffect, useState } from 'react';
function App() {
const [workouts, setWorkouts] = useState([]);
const fetchWorkouts = async () => {
try {
const response = await fetch('http://localhost:4000/api/workouts');
const data = await response.json();
setWorkouts(data);
} catch (error) {
console.error('Error:', error);
}
};
useEffect(() => {
fetchWorkouts();
}, []);
return (
<div className="App">
<h1>Workouts</h1>
{workouts.length === 0 ? (
<p>Geen workouts gevonden</p>
) : (
workouts.map(workout => (
<div key={workout._id}>
<h3>{workout.title}</h3>
<p>Reps: {workout.reps}</p>
<p>Load: {workout.load} kg</p>
</div>
))
)}
</div>
);
}
export default App;
useState([])- Bewaar workouts in stateuseEffect- Draait automatisch bij ladenfetchWorkouts- Async functie voor data ophalenawait fetch('...')- Wacht op response van backendawait response.json()- Wacht tot JSON geparsed istry/catch- Vangt errors op.map()- Loopt door workouts en toont ze
Testen
Open http://localhost:5173 in browser.
Het werkt als:
- Je ziet "Workouts" als titel
- Je ziet workouts uit database
- Geen CORS errors in console (F12 → Console)
Veelvoorkomende Problemen:
1. CORS error nog steeds
- Check CORS in server.js
- Herstart backend
- Check URL:
origin: 'http://localhost:5173'
2. "Geen workouts gevonden"
- Database is leeg
- Maak workouts met Postman (POST request)
3. Backend draait niet
- Check terminal 1 of backend draait
- Start opnieuw:
npm run dev
Bonus: POST Request vanuit React
Je kunt ook nieuwe workouts aanmaken vanuit React. Voorbeeld:
import { useState } from 'react';
function WorkoutForm() {
const [title, setTitle] = useState('');
const [reps, setReps] = useState('');
const [load, setLoad] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const workout = {
title,
reps: Number(reps),
load: Number(load)
};
const response = await fetch('http://localhost:4000/api/workouts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(workout)
});
const data = await response.json();
if (response.ok) {
console.log('Workout aangemaakt!', data);
// Reset form
setTitle('');
setReps('');
setLoad('');
} else {
console.error('Error:', data.error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Titel"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="number"
placeholder="Reps"
value={reps}
onChange={(e) => setReps(e.target.value)}
/>
<input
type="number"
placeholder="Load (kg)"
value={load}
onChange={(e) => setLoad(e.target.value)}
/>
<button type="submit">Toevoegen</button>
</form>
);
}
useState- Voor elk input veldhandleSubmit- Stuurt data naar backend bij submitmethod: 'POST'- Nieuwe data aanmakenheaders- Vertelt backend dat we JSON sturenbody: JSON.stringify(workout)- Zet object om naar JSON- Reset form als gelukt
UPDATE Request vanuit React
Een workout aanpassen. Je hebt het ID nodig van de workout die je wilt aanpassen.
import { useState } from 'react';
function UpdateWorkout({ workoutId, currentTitle, currentReps, currentLoad }) {
const [title, setTitle] = useState(currentTitle);
const [reps, setReps] = useState(currentReps);
const [load, setLoad] = useState(currentLoad);
const handleUpdate = async (e) => {
e.preventDefault();
const updatedWorkout = {
title,
reps: Number(reps),
load: Number(load)
};
try {
const response = await fetch(`http://localhost:4000/api/workouts/${workoutId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedWorkout)
});
const data = await response.json();
if (response.ok) {
console.log('Workout aangepast!', data);
} else {
console.error('Error:', data.error);
}
} catch (error) {
console.error('Fetch error:', error);
}
};
return (
<form onSubmit={handleUpdate}>
<input
type="text"
placeholder="Titel"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="number"
placeholder="Reps"
value={reps}
onChange={(e) => setReps(e.target.value)}
/>
<input
type="number"
placeholder="Load (kg)"
value={load}
onChange={(e) => setLoad(e.target.value)}
/>
<button type="submit">Aanpassen</button>
</form>
);
}
workoutId- Props met ID van workout die aangepast wordtuseState(currentTitle)- Pre-fill form met huidige waardenmethod: 'PATCH'- UPDATE operatie`/api/workouts/${workoutId}`- URL met specifiek IDtry/catch- Vangt errors op
Gebruik in App.jsx:
// In je workout lijst
<UpdateWorkout
workoutId={workout._id}
currentTitle={workout.title}
currentReps={workout.reps}
currentLoad={workout.load}
/>
DELETE Request vanuit React
Een workout verwijderen. Simpeler dan UPDATE omdat je geen form data hoeft mee te sturen.
function DeleteWorkout({ workoutId, workoutTitle }) {
const handleDelete = async () => {
// Bevestiging vragen
if (!confirm(`Weet je zeker dat je "${workoutTitle}" wilt verwijderen?`)) {
return;
}
try {
const response = await fetch(`http://localhost:4000/api/workouts/${workoutId}`, {
method: 'DELETE'
});
const data = await response.json();
if (response.ok) {
console.log('Workout verwijderd!', data);
// Verwijder uit UI of refresh lijst
} else {
console.error('Error:', data.error);
}
} catch (error) {
console.error('Fetch error:', error);
}
};
return (
<button onClick={handleDelete}>
Verwijderen
</button>
);
}
confirm()- Browser popup voor bevestigingmethod: 'DELETE'- DELETE operatieGeen body nodig- DELETE heeft alleen ID in URLtry/catch- Vangt errors op
Belangrijk!
Vergeet niet de workout uit je state te verwijderen na succesvolle DELETE, anders blijft hij in de UI staan terwijl hij uit de database is!
Volledig Voorbeeld: App met CRUD
Hier is een compleet voorbeeld met alle CRUD operaties in één component:
import { useEffect, useState } from 'react';
function App() {
const [workouts, setWorkouts] = useState([]);
const [title, setTitle] = useState('');
const [reps, setReps] = useState('');
const [load, setLoad] = useState('');
// READ - Haal alle workouts op
const fetchWorkouts = async () => {
try {
const response = await fetch('http://localhost:4000/api/workouts');
const data = await response.json();
setWorkouts(data);
} catch (error) {
console.error('Error:', error);
}
};
useEffect(() => {
fetchWorkouts();
}, []);
// CREATE - Nieuwe workout toevoegen
const handleSubmit = async (e) => {
e.preventDefault();
const workout = {
title,
reps: Number(reps),
load: Number(load)
};
try {
const response = await fetch('http://localhost:4000/api/workouts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workout)
});
const data = await response.json();
if (response.ok) {
setWorkouts([data, ...workouts]); // Voeg toe aan lijst
setTitle('');
setReps('');
setLoad('');
}
} catch (error) {
console.error('Error:', error);
}
};
// DELETE - Workout verwijderen
const handleDelete = async (id) => {
if (!confirm('Weet je het zeker?')) return;
try {
const response = await fetch(`http://localhost:4000/api/workouts/${id}`, {
method: 'DELETE'
});
if (response.ok) {
setWorkouts(workouts.filter(w => w._id !== id)); // Verwijder uit lijst
}
} catch (error) {
console.error('Error:', error);
}
};
return (
<div className="App">
<h1>Workouts</h1>
{/* CREATE Form */}
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Titel"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="number"
placeholder="Reps"
value={reps}
onChange={(e) => setReps(e.target.value)}
/>
<input
type="number"
placeholder="Load (kg)"
value={load}
onChange={(e) => setLoad(e.target.value)}
/>
<button type="submit">Toevoegen</button>
</form>
{/* READ - Toon workouts */}
{workouts.length === 0 ? (
<p>Geen workouts gevonden</p>
) : (
workouts.map(workout => (
<div key={workout._id}>
<h3>{workout.title}</h3>
<p>Reps: {workout.reps}</p>
<p>Load: {workout.load} kg</p>
<button onClick={() => handleDelete(workout._id)}>
Verwijderen
</button>
</div>
))
)}
</div>
);
}
export default App;
- READ: Haalt workouts op bij laden (useEffect)
- CREATE: Voegt nieuwe workout toe via form
- DELETE: Verwijdert workout met button
- State management: Na CREATE/DELETE wordt state geupdate
setWorkouts([data, ...workouts])- Voeg nieuwe workout bovenaan toefilter(w => w._id !== id)- Verwijder workout uit array
Dit is een werkende CRUD app!
Je kunt nu:
- ✅ Workouts bekijken (READ)
- ✅ Workouts toevoegen (CREATE)
- ✅ Workouts verwijderen (DELETE)
- ✅ Workouts wijzigen (UPDATE)
Samenvatting
Je hebt nu volledig werkende MERN app:
Backend (Express):
- MongoDB database verbinding
- Mongoose model en schema
- API routes voor CRUD operaties
- CORS configuratie
Frontend (React):
- Vite development server
- Data ophalen met fetch
- Data tonen met map()
- Formulier voor nieuwe data
Volgende Stappen
Nu CORS en frontend werken, kun je verder met:
- Authentication toevoegen
- Styling met CSS