REST in 2026: best practices, versiebeheer en het Richardson Maturity Model
De meeste API's die zichzelf "RESTful" noemen, zijn dat niet. Ze gebruiken HTTP-achtige transport, JSON als formaat, en stop daar. De Richardson-volwassenheidsmodel, geïntroduceerd door Leonard Richardson in 2008 en gepopulariseerd door Martin Fowler, biedt een schaal in vier niveaus die een generieke HTTP API (niveau 0) onderscheidt van een echte API RUSTIG (niveau 3 met HATEOAS).
Maar architectonisch perfectionisme is niet het doel van deze gids. Het doel is om jou les te geven om REST API's te bouwen die dat wel zijn correct, onderhoudbaar en goed gedocumenteerd: gebruik de juiste HTTP-semantiek, implementeer in de loop van de tijd duurzaam versiebeheer, beheer effectief cachen met ETag en het contract documenteren met OpenAPI 3.1. Dit zijn de echte onderscheidende factoren tussen een professionele API en een API die problemen veroorzaakt voor consumenten.
Wat je gaat leren
- De 4 niveaus van het Richardson Maturity Model met concrete voorbeelden
- Correcte HTTP-semantiek: methoden, statuscodes, headers
- Idempotentie en veilige methoden: de theoretische basis met praktische toepassingen
- Versiebeheerstrategieën: URI, header en versiebeheer via additieve wijzigingen
- ETag en voorwaardelijke verzoeken voor efficiënte caching
- OpenAPI 3.1: documentstructuur en best practices
Richardson volwassenheidsmodel: de 4 niveaus
Het Richardson Maturity Model meet de ‘RESTfulness’ van een API op schaal 0 tot 3. Het begrijpen van deze niveaus helpt bij het identificeren van hiaten in de architectuur in bestaande API’s en bouw nieuwe op het juiste niveau.
Niveau 0: HTTP als transport (tunneling)
// Livello 0: tutto su un unico endpoint, azione nel body
POST /api
Content-Type: application/json
{
"action": "getUser",
"id": 123
}
POST /api
{
"action": "createUser",
"name": "Federico",
"email": "federico@example.com"
}
// HTTP e solo un canale: la semantica e tutta nell'applicazione
// Nessun caching possibile, nessuna semantica uniforme
Niveau 1: Bronnen (betekenisvolle URI)
// Livello 1: URL per risorse, ma ancora solo POST
POST /users/123 // Non ha senso: POST per leggere?
POST /users/create
POST /users/delete/123
POST /users/get/all
// Migliore, ma i verbi HTTP non sono usati semanticamente
Niveau 2: HTTP-werkwoorden (het standaardniveau)
// Livello 2: URL meaningful + verbi HTTP corretti + status codes
GET /users -> 200 OK con lista
GET /users/123 -> 200 OK con utente, 404 Not Found
POST /users -> 201 Created con Location header
PUT /users/123 -> 200 OK aggiornato, 404 Not Found
PATCH /users/123 -> 200 OK parzialmente aggiornato
DELETE /users/123 -> 204 No Content, 404 Not Found
// Questo e il livello che la maggior parte delle API raggiunge
// ed e generalmente sufficiente per la produzione
Niveau 3: HATEOAS (Hypermedia als motor van applicatiestatus)
// Livello 3: ogni risposta contiene link alle azioni disponibili
GET /users/123
-> 200 OK
{
"id": 123,
"name": "Federico",
"email": "federico@example.com",
"status": "active",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"deactivate": { "href": "/users/123/deactivate", "method": "POST" },
"update": { "href": "/users/123", "method": "PUT" }
}
}
// Il client non deve "sapere" a priori gli URL: li scopre dalle risposte
// Permette di cambiare URL senza rompere i client (in teoria)
HATEOAS: Is het het waard?
HATEOAS is theoretisch elegant, maar wordt in de praktijk zelden geïmplementeerd. De redenen:
- Verhoogt de responsgrootte aanzienlijk
- Clients 'hardcoderen' URL's echter vaak voor prestaties
- Vereist specifieke tools om door links te navigeren
- De meeste teams gebruiken OpenAPI als contract
Niveau 2 + OpenAPI 3.1 en de pragmatische ‘sweet spot’ voor de meeste API’s in 2026. HATEOAS is vooral zinvol in openbare API's waar URL-stabiliteit langdurig is termijn en kritiek.
Correcte HTTP-semantiek
Het gebruik van HTTP-werkwoorden met de juiste semantiek is niet alleen een esthetische kwestie: het heeft impact caching, idempotentie en de mogelijkheid van clients om verzoeken veilig in te trekken.
// Proprietà dei metodi HTTP
Metodo | Safe | Idempotente | Body richiesta | Uso corretto
--------|------|-------------|----------------|----------------------------------
GET | SI | SI | No | Lettura, query, ricerca
HEAD | SI | SI | No | Verifica esistenza, metadata
OPTIONS | SI | SI | No | CORS preflight, capabilities
POST | NO | NO | SI | Creazione, azioni non-idempotenti
PUT | NO | SI | SI | Sostituzione completa di risorsa
PATCH | NO | NO* | SI | Modifica parziale
DELETE | NO | SI | Opzionale | Eliminazione
// *PATCH puo essere reso idempotente con patch semantics JSON Patch (RFC 6902)
Veilig het betekent dat het verzoek geen neveneffecten aan de serverzijde heeft (de server verandert niet van status). Een client kan zonder gevolgen op F5 drukken op een GET.
Idempotent betekent dat meerdere identieke verzoeken hetzelfde opleveren resultaat van één enkel verzoek. Cruciaal voor nieuwe pogingen: als het netwerk uitvalt tijdens een DELETE, de klant kan het opnieuw proberen zonder bang te hoeven zijn dat hij meer dan één keer wordt verwijderd.
Statuscodes: gebruik de juiste
Het misbruik van 200 OK voor alles (inclusief lichaamsfouten) en een van de antipatronen
gebruikelijker. Door de juiste codes te gebruiken, kunnen klanten, proxy's en monitoringtools dat doen
interpreteer de antwoorden correct:
// Status codes piu importanti con esempi di uso corretto
// 2xx: Success
200 OK - GET, PUT, PATCH con risorsa nel body
201 Created - POST che crea una risorsa (+ Location header)
202 Accepted - Operazione asincrona accettata (non ancora completata)
204 No Content - DELETE, PUT/PATCH senza body di risposta
// 3xx: Redirection
301 Moved Permanently - Redirect permanente (aggiornare i bookmark)
302 Found - Redirect temporaneo
304 Not Modified - ETag/If-None-Match: risorsa non cambiata, usa la cache
// 4xx: Client Error (il client ha sbagliato)
400 Bad Request - Input malformato, schema validation fallita
401 Unauthorized - Non autenticato (serve login)
403 Forbidden - Autenticato ma non autorizzato
404 Not Found - Risorsa non trovata
405 Method Not Allowed - Metodo HTTP non supportato su questo endpoint
409 Conflict - Conflitto di stato (es: email gia esistente)
410 Gone - Risorsa eliminata permanentemente (vs 404)
422 Unprocessable Entity - Sintassi ok ma semantica invalida
429 Too Many Requests - Rate limit raggiunto (+ Retry-After header)
// 5xx: Server Error (colpa del server)
500 Internal Server Error - Errore generico non gestito
502 Bad Gateway - Errore dal backend upstream
503 Service Unavailable - Server temporaneamente non disponibile
504 Gateway Timeout - Timeout dal backend upstream
PUT versus PATCH: het belangrijke onderscheid
De verwarring tussen PUT en PATCH komt veel voor, maar heeft concrete implicaties:
// PUT: sostituzione COMPLETA della risorsa (idempotente)
// Se ometti un campo, viene azzerato!
PUT /users/123
{
"name": "Federico Calo",
"email": "federico@example.com"
// Se il campo "phone" non e incluso, viene rimosso!
}
// PATCH: modifica PARZIALE (solo i campi specificati)
PATCH /users/123
{
"name": "Federico Calo"
// Solo name viene aggiornato, email e phone rimangono invariati
}
// PATCH con JSON Patch (RFC 6902): piu preciso e idempotente
PATCH /users/123
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/name", "value": "Federico Calo" },
{ "op": "add", "path": "/phone", "value": "+39 333 1234567" },
{ "op": "remove", "path": "/tempNote" }
]
Versiebeheerstrategieën
Versiebeheer is een van de belangrijkste beslissingen bij het ontwerpen van een openbare API. Een keer dat klanten afhankelijk zijn van een API, het wijzigen van het contract verbreekt hun applicaties. Le De belangrijkste strategieën hebben verschillende afwegingen:
1. URI-versiebeheer (meest gebruikelijk)
GET /api/v1/users // Versione 1
GET /api/v2/users // Versione 2 con campi aggiuntivi
// Vantaggi:
// - Visibile e ovvio
// - Cacheable a livello HTTP (l'URL e diverso)
// - Facile da esplorare con il browser
// - Semplice da loggare e monitorare
// Svantaggi:
// - "Sporco" semanticamente (la versione non e parte della risorsa)
// - Proliferazione di URL nel tempo
2. Versiebeheer van headers
GET /api/users
Accept: application/vnd.myapi.v2+json
// oppure
API-Version: 2
// Vantaggi:
// - URL "puliti"
// - Piu vicino alla semantica HTTP originale
// Svantaggi:
// - Non cacheable con HTTP standard (Cache-Vary header necessario)
// - Invisibile dalla URL (difficile da debuggare)
// - Meno intuitivo per i nuovi consumatori dell'API
3. Versiebeheer via additieve wijzigingen (beste)
// Strategia: non cambiare mai, solo aggiungere (non rompere mai i client)
// V1 risposta:
GET /api/users/123
{
"id": 123,
"name": "Federico",
"email": "federico@example.com"
}
// Aggiungi campi senza versione (i client vecchi ignorano i nuovi campi):
GET /api/users/123
{
"id": 123,
"name": "Federico",
"email": "federico@example.com",
"createdAt": "2025-01-15T10:30:00Z", // AGGIUNTO: non rompe i client vecchi
"avatarUrl": null // AGGIUNTO: nullable per retrocompat
}
// Quando DEVI rompere (rare):
// - Rimuovere un campo -> versione nuova
// - Cambiare tipo di un campo -> versione nuova
// - Cambiare semantica di un campo -> versione nuova
ETag en voorwaardelijke verzoeken
L'Etag (Entity Tag) en een HTTP-mechanisme om de cache te beheren en optimistische concurrentie. Elke bron heeft een hash of tijdstempel die de versie ervan identificeert huidig:
// Server: risposta con ETag
GET /users/123
->
200 OK
ETag: "abc123def456"
Cache-Control: max-age=300
{
"id": 123,
"name": "Federico",
"version": 3
}
// Client: richiesta condizionale con If-None-Match
GET /users/123
If-None-Match: "abc123def456"
->
304 Not Modified // Risorsa non cambiata, usa la cache!
// Nessun body = traffico ridotto
// Se la risorsa e cambiata:
GET /users/123
If-None-Match: "abc123def456"
->
200 OK
ETag: "xyz789new123" // Nuovo ETag
{ /* dati aggiornati */ }
// ETag per concorrenza ottimistica (prevenire aggiornamenti in conflitto):
PUT /users/123
If-Match: "abc123def456" // "Aggiorna SOLO se la versione e ancora questa"
{...}
->
200 OK // Aggiornamento riuscito, nessun conflitto
// oppure
412 Precondition Failed // Qualcun altro ha modificato la risorsa!
// Il client deve rileggere prima di riaggiornare
Documenteren met OpenAPI 3.1
OpenAPI 3.1 is de industriestandaard voor het documenteren van REST API's. Een mooi OpenAPI-document geschreven dient als een formeel contract, maakt het genereren van SDK's voor de klant mogelijk en biedt bevoegdheden Interactieve documentatie met Swagger UI of Redoc:
// openapi.yaml - Struttura base di un documento OpenAPI 3.1
openapi: 3.1.0
info:
title: User Management API
version: 1.2.0
description: |
API per la gestione degli utenti dell'applicazione.
## Autenticazione
Usa Bearer token JWT nell'header Authorization.
contact:
name: API Support
email: api@example.com
license:
name: MIT
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
paths:
/users:
get:
operationId: listUsers
summary: Lista utenti
tags: [Users]
parameters:
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
- name: search
in: query
schema: { type: string }
responses:
'200':
description: Lista utenti paginata
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createUser
summary: Crea utente
tags: [Users]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: Utente creato
headers:
Location:
schema: { type: string }
description: URL del nuovo utente
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: Email gia esistente
components:
schemas:
User:
type: object
required: [id, name, email, createdAt]
properties:
id: { type: integer, format: int64, readOnly: true }
name: { type: string, minLength: 1, maxLength: 100 }
email: { type: string, format: email }
createdAt: { type: string, format: date-time, readOnly: true }
CreateUserRequest:
type: object
required: [name, email, password]
properties:
name: { type: string, minLength: 1, maxLength: 100 }
email: { type: string, format: email }
password: { type: string, minLength: 8, writeOnly: true }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
Patroon voor foutreacties
Een consistente structuur voor foutreacties verbetert de ontwikkelaar aanzienlijk API-consumentenervaring. De standaard RFC 9457 (Probleemdetails) en werd in 2026 de voorkeurskeuze:
// RFC 9457 Problem Details for HTTP APIs
// Content-Type: application/problem+json
// 400 Bad Request
{
"type": "https://example.com/errors/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "The request body contains invalid data",
"instance": "/api/users",
"errors": [
{
"field": "email",
"message": "Invalid email format",
"value": "not-an-email"
},
{
"field": "password",
"message": "Password must be at least 8 characters",
"value": null
}
]
}
// 409 Conflict
{
"type": "https://example.com/errors/duplicate-email",
"title": "Duplicate Email",
"status": 409,
"detail": "An account with this email already exists",
"instance": "/api/users",
"email": "federico@example.com"
}
// 429 Too Many Requests
{
"type": "https://example.com/errors/rate-limited",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Too many requests. Retry after 60 seconds.",
"retryAfter": 60
}
Conclusies en volgende stappen
Een professionele REST API in 2026 opereert op niveau 2 van het Richardson Maturity Model (bronnen + correcte HTTP-werkwoorden + passende statuscodes), gebruik ETag voor caching en optimistische gelijktijdigheid, hanteert duurzaam versiebeheer op basis van additieve wijzigingen, en documenteert het formele contract met OpenAPI 3.1. In de meeste contexten is het niet nodig om HATEOAS-niveau 3 te bereiken.
Het volgende artikel onderzoekt GraphQL diepgaand: hoe het systeem werkt van solvers, omdat het N+1-probleem het meest voorkomende architecturale risico is, en als DataLoader lost batching van databasequery's op.
Serie: API-ontwerp — REST, GraphQL, gRPC en tRPC vergeleken
- Artikel 1: Het API-landschap in 2026 – Beslissingsmatrix
- Artikel 2 (dit): REST in 2026 – Best Practice, versiebeheer en het Richardson Maturity Model
- Artikel 3: GraphQL — Querytaal, Resolver en het N+1-probleem
- Artikel 4: GraphQL Federatie — Supergraph, Subgraph en Apollo Router
- Artikel 5: gRPC — Protobuf, prestaties en service-to-service-communicatie
- Artikel 6: tRPC — Typeveiligheid end-to-end zonder codegeneratie
- Artikel 7: Webhooks — Patronen, beveiliging en logica voor opnieuw proberen
- Artikel 8: API-versiebeheer — URI, koptekst en beëindigingsbeleid
- Artikel 9: Snelheidsbeperking en -beperking — Algoritmen en implementaties
- Artikel 10: Hybride API-architectuur — REST + tRPC + gRPC in 2026







