Les 2 · Module 1 · API-basis
REST-ontwerp: resources, methodes en statuscodes
Ontwerp de URL's van Winkel.Api volgens REST: resources als zelfstandige naamwoorden, de juiste HTTP-methode, idempotentie, statuscodes en versiebeheer.
🎯 Na deze les kun je
- resources en URL's ontwerpen met zelfstandige naamwoorden
- de juiste HTTP-methode kiezen en uitleggen wat veilig en idempotent betekent
- de passende statuscode kiezen voor succes en fouten
- een versiestrategie voor je API kiezen
Een API is een contract: andere programma's bouwen erop, en elke wijziging kan ze breken. Een goed ontwerp maakt je API voorspelbaar: wie één endpoint kent, kan de rest raden. REST is geen standaard maar een set afspraken die bijna elke openbare API volgt. In deze les ontwerp je de URL's voor Winkel.Api, voordat je verder bouwt.
Resources, geen werkwoorden
Een URL wijst een ding aan (een resource), de HTTP-methode zegt wat je ermee doet.
| Slecht | Goed |
|---|---|
GET /haalProductenOp |
GET /producten |
POST /maakProduct |
POST /producten |
POST /verwijderProduct?id=3 |
DELETE /producten/3 |
GET /producten/3/getReviews |
GET /producten/3/reviews |
Het ontwerp voor de winkel:
/producten collectie
/producten/{id} één product
/producten/{id}/reviews reviews van een product (sub-resource)
/categorieen
/bestellingen
/bestellingen/{id}/regels
Kies één taal en blijf erbij (hier Nederlands), meervoud voor collecties, kleine letters, koppeltekens.
De methodes
| Methode | Doel | Veilig? | Idempotent? |
|---|---|---|---|
| GET | ophalen | ja | ja |
| POST | nieuw item in collectie, of een actie | nee | nee |
| PUT | item volledig vervangen | nee | ja |
| PATCH | item deels wijzigen | nee | niet per se |
| DELETE | verwijderen | nee | ja |
- Veilig: verandert niets op de server. Een GET mag nooit iets wijzigen (zoekmachines en prefetch volgen links).
- Idempotent: twee keer uitvoeren heeft hetzelfde effect als één keer. Belangrijk bij netwerkproblemen: een client mag een PUT of DELETE gerust herhalen.
Voorspel eerst: een client stuurt POST /bestellingen, krijgt een timeout, en probeert het opnieuw. Wat kan er misgaan, en
waarom heeft PUT dat probleem niet?
Uitkomst
Misschien was de eerste POST wél aangekomen: dan heeft de klant twee bestellingen. POST is niet idempotent. Een PUT /producten/3
twee keer zet het product twee keer op dezelfde waarde, met hetzelfde resultaat. Voor POST gebruiken API's daarom een
Idempotency-Key-header: de server onthoudt de sleutel en voert een herhaling niet nog eens uit (les 13).
Statuscodes
| Code | Gebruik |
|---|---|
| 200 OK | gelukt, met inhoud |
| 201 Created | aangemaakt; Location wijst naar het nieuwe item |
| 204 No Content | gelukt, geen inhoud (DELETE, soms PUT) |
| 400 Bad Request | invoer klopt niet (validatie, les 5) |
| 401 Unauthorized | niet ingelogd / geen geldige token |
| 403 Forbidden | wel ingelogd, geen recht |
| 404 Not Found | bestaat niet |
| 409 Conflict | botst met de huidige toestand (dubbele naam, oude versie) |
| 422 Unprocessable Entity | syntaxis goed, inhoud niet verwerkbaar (sommige API's) |
| 500 Internal Server Error | fout in jouw code; nooit bewust teruggeven |
Een fout geef je niet terug als 200 met {"succes": false}: dan kunnen clients, proxies en monitoring het verschil niet zien.
Filteren, sorteren en pagineren via de query
GET /producten?categorie=toetsenborden&maxPrijs=50&sorteer=-prijs&pagina=2&perPagina=20
Het pad wijst de resource aan; de query bepaalt welk deel en in welke volgorde. Uitwerking in les 12.
Versies
Je API verandert. Een brekende wijziging (veld hernoemen, verplicht veld toevoegen) mag bestaande clients niet stukmaken.
- URL-versie:
/v1/producten– simpel en zichtbaar; de meest gebruikte keuze. - Header-versie:
Api-Version: 2– nettere URL's, lastiger te testen.
Toevoegen (een nieuw optioneel veld, een nieuw endpoint) is niet brekend. Verwijderen of hernoemen wel.
var v1 = app.MapGroup("/v1");
v1.MapGet("/producten", ...);
Typische fouten
- Werkwoorden in URL's en dus voor elke actie een nieuw endpoint.
- GET die iets wijzigt, bijvoorbeeld
GET /producten/3/verwijder. - Overal 200 met een foutveld, of 500 voor gewone invoerfouten.
Oefenen
- Ontwerp de endpoints voor reviews: lijst per product, toevoegen, eigen review wijzigen en verwijderen. Geef methode, URL en statuscodes.
- Hoe zou je "bestelling annuleren" modelleren zonder werkwoord in de URL? Geef twee opties.
- Zet de endpoints uit les 1 in een groep
/v1en voeg de juiste statuscodes toe. - Welke van deze wijzigingen zijn brekend? (a) veld
omschrijvingtoevoegen, (b)naamhernoemen naartitel, (c) nieuw endpoint/v1/categorieen, (d)prijsvan getal naar string.
Modelantwoord oefening 1
| Methode + URL | Succes | Fouten |
|---|---|---|
GET /v1/producten/{id}/reviews |
200 | 404 (product) |
POST /v1/producten/{id}/reviews |
201 + Location | 400, 401, 404, 409 (al een review) |
PUT /v1/reviews/{id} |
200 of 204 | 400, 401, 403 (niet van jou), 404 |
DELETE /v1/reviews/{id} |
204 | 401, 403, 404 |
Eén review heeft een eigen URL (/reviews/{id}): je hoeft het product niet te kennen om hem te wijzigen.
Modelantwoord oefening 2
(1) PATCH /v1/bestellingen/{id} met {"status": "geannuleerd"}: de status is een eigenschap. (2) POST /v1/bestellingen/{id}/annulering:
een annulering is een eigen resource (met datum, reden). Beide zijn gangbaar; optie 2 is duidelijker als annuleren regels heeft.
Modelantwoord oefening 3
var v1 = app.MapGroup("/v1/producten");
v1.MapGet("/", () => producten);
v1.MapGet("/{id:int}", (int id) => producten.FirstOrDefault(p => p.Id == id) is { } p ? Results.Ok(p) : Results.NotFound());
v1.MapPost("/", (Product nieuw) => producten.Any(p => p.Id == nieuw.Id)
? Results.Conflict()
: Results.Created($"/v1/producten/{nieuw.Id}", Voeg(nieuw)));
Modelantwoord oefening 4
(a) niet brekend, (b) brekend, (c) niet brekend, (d) brekend: clients die een getal verwachten, gaan stuk.
Lokaal oefenen
Geen live lab. Bekijk ter vergelijking de documentatie van een openbare API (bijvoorbeeld die van GitHub of Mollie) en zoek de patronen uit deze les.
Samenvatting
- URL's zijn zelfstandige naamwoorden; de methode is het werkwoord.
- GET is veilig; PUT en DELETE zijn idempotent; POST niet.
- Gebruik statuscodes eerlijk, filter via de query, versie via
/v1. - Volgende les: minimal API's of controllers?