REST API Grundlagen: HTTP-Methoden, Statuscodes, HATEOAS & Richardson Maturity Model
Dieser Beitrag ist eine umfassende Einführung in die REST API Grundlagen – inklusive HTTP-Methoden, Statuscodes, HATEOAS und Richardson Maturity Model mit praktischen Beispielen.
In a Nutshell
REST APIs nutzen HTTP-Methoden für CRUD-Operationen, standardisierte Statuscodes für Ergebnisse, HATEOAS für discoverable APIs und folgen dem Richardson Maturity Model für Reifegrade.
Kompakte Fachbeschreibung
REST (Representational State Transfer) ist ein architektonischer Stil für verteilte Systeme, der HTTP-Protokoll und Standardmethoden nutzt.
HTTP-Methoden:
- GET: Ressource abfragen (sicher, idempotent)
- POST: Ressource erstellen (nicht sicher, nicht idempotent)
- PUT: Ressource aktualisieren/ersetzen (nicht sicher, idempotent)
- PATCH: Ressource teilweise aktualisieren (nicht sicher, nicht idempotent)
- DELETE: Ressource löschen (nicht sicher, idempotent)
Statuscodes:
- 2xx: Erfolgreiche Operationen (200, 201, 204)
- 3xx: Weiterleitungen (301, 302, 304)
- 4xx: Client-Fehler (400, 401, 403, 404, 422)
- 5xx: Server-Fehler (500, 502, 503)
HATEOAS: Hypermedia as the Engine of Application State - APIs sind selbstentdeckend durch Hyperlinks.
Prüfungsrelevante Stichpunkte
- REST: Architekturstil für Web Services mit HTTP
- HTTP-Methoden: GET, POST, PUT, PATCH, DELETE für CRUD
- Statuscodes: Standardisierte Antwortcodes (2xx, 3xx, 4xx, 5xx)
- HATEOAS: Hypermedia-basierte Navigation zwischen Ressourcen
- Richardson Maturity Model: Reifegrade für REST APIs (0-3)
- Idempotenz: Mehrfachaufrufe haben gleiche Wirkung
- Stateless: Kein Server-seitiger Zustand
- IHK-relevant: Moderne Web-Service-Architektur
Kernkomponenten
- Ressourcen: Identifizierbare Entitäten mit URIs
- HTTP-Methoden: Standardisierte Operationen
- Statuscodes: Einheitliche Antwortformatierung
- Representations: JSON, XML, HTML, etc.
- HATEOAS: Hypermedia-Navigation
- Statelessness: Zustandslose Kommunikation
- Cacheability: HTTP-Caching-Header
- Uniform Interface: Konsistente API-Konventionen
Praxisbeispiele
1. REST API mit Spring Boot (Java)
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import org.springframework.stereotype.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
// Datenmodell
class User {
private Long id;
private String name;
private String email;
private Map<String, String> _links = new HashMap<>();
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getter und Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Map<String, String> get_links() { return _links; }
public void set_links(Map<String, String> links) { this._links = links; }
}
// Repository (In-Memory für Demo)
@Repository
class UserRepository {
private final Map<Long, User> users = new ConcurrentHashMap<>();
private long nextId = 1;
public List<User> findAll() {
return new ArrayList<>(users.values());
}
public Optional<User> findById(Long id) {
return Optional.ofNullable(users.get(id));
}
public User save(User user) {
if (user.getId() == null) {
user.setId(nextId++);
}
users.put(user.getId(), user);
return user;
}
public void deleteById(Long id) {
users.remove(id);
}
public boolean existsById(Long id) {
return users.containsKey(id);
}
}
// REST Controller
@RestController
@RequestMapping("/api/users")
class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
// GET /api/users - Alle Benutzer abrufen
@GetMapping
public ResponseEntity<Map<String, Object>> getAllUsers() {
List<User> users = userRepository.findAll();
// HATEOAS Links hinzufügen
for (User user : users) {
addHateoasLinks(user);
}
Map<String, Object> response = new HashMap<>();
response.put("users", users);
response.put("_links", Map.of(
"self", Map.of("href", "/api/users"),
"create", Map.of("href", "/api/users", "method", "POST")
));
response.put("count", users.size());
return ResponseEntity.ok(response);
}
// GET /api/users/{id} - Benutzer abrufen
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable Long id) {
return userRepository.findById(id)
.map(user -> {
addHateoasLinks(user);
return ResponseEntity.ok(user);
})
.orElse(ResponseEntity.notFound().build());
}
// POST /api/users - Benutzer erstellen
@PostMapping
public ResponseEntity<Map<String, Object>> createUser(@RequestBody User user) {
// Validierung
if (user.getName() == null || user.getName().trim().isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Name darf nicht leer sein"));
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Ungültige E-Mail-Adresse"));
}
User savedUser = userRepository.save(user);
addHateoasLinks(savedUser);
Map<String, Object> response = new HashMap<>();
response.put("user", savedUser);
response.put("message", "Benutzer erfolgreich erstellt");
response.put("_links", Map.of(
"self", Map.of("href", "/api/users/" + savedUser.getId()),
"all", Map.of("href", "/api/users")
));
return ResponseEntity
.status(HttpStatus.CREATED)
.body(response);
}
// PUT /api/users/{id} - Benutzer vollständig aktualisieren
@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody User user) {
if (!userRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
user.setId(id);
User updatedUser = userRepository.save(user);
addHateoasLinks(updatedUser);
Map<String, Object> response = new HashMap<>();
response.put("user", updatedUser);
response.put("message", "Benutzer erfolgreich aktualisiert");
return ResponseEntity.ok(response);
}
// PATCH /api/users/{id} - Benutzer teilweise aktualisieren
@PatchMapping("/{id}")
public ResponseEntity<?> partialUpdateUser(@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
return userRepository.findById(id)
.map(user -> {
// Nur angegebene Felder aktualisieren
if (updates.containsKey("name")) {
user.setName((String) updates.get("name"));
}
if (updates.containsKey("email")) {
user.setEmail((String) updates.get("email"));
}
User updatedUser = userRepository.save(user);
addHateoasLinks(updatedUser);
Map<String, Object> response = new HashMap<>();
response.put("user", updatedUser);
response.put("message", "Benutzer teilweise aktualisiert");
return ResponseEntity.ok(response);
})
.orElse(ResponseEntity.notFound().build());
}
// DELETE /api/users/{id} - Benutzer löschen
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteUser(@PathVariable Long id) {
if (!userRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
userRepository.deleteById(id);
Map<String, Object> response = new HashMap<>();
response.put("message", "Benutzer erfolgreich gelöscht");
response.put("_links", Map.of(
"all", Map.of("href", "/api/users")
));
return ResponseEntity.ok(response);
}
// HATEOAS Links hinzufügen
private void addHateoasLinks(User user) {
Map<String, String> links = new HashMap<>();
links.put("self", "/api/users/" + user.getId());
links.put("collection", "/api/users");
links.put("update", "/api/users/" + user.getId());
links.put("delete", "/api/users/" + user.getId());
user.set_links(links);
}
}
// Exception Handler
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(Map.of("error", e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleGenericException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Interner Serverfehler"));
}
}
// Spring Boot Application
@SpringBootApplication
public class RestApiApplication {
public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
}
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// Test-Daten erstellen
userRepository.save(new User(null, "Alice", "alice@example.com"));
userRepository.save(new User(null, "Bob", "bob@example.com"));
userRepository.save(new User(null, "Charlie", "charlie@example.com"));
System.out.println("Test-Daten erstellt");
};
}
}
2. Richardson Maturity Model Demo
// Richardson Maturity Model Level 0: Swamp of POX
class Level0Api {
private String baseUrl;
public Level0Api(String baseUrl) {
this.baseUrl = baseUrl;
}
// Keine HTTP-Methoden, nur POST für alles
public String createUser(String name, String email) {
// POST /api - Keine REST-Konventionen
String payload = String.format("{\"action\": \"create\", \"name\": \"%s\", \"email\": \"%s\"}",
name, email);
return postRequest("/api", payload);
}
public String getUser(long id) {
// POST /api - Keine URI-Ressourcen
String payload = String.format("{\"action\": \"get\", \"id\": %d}", id);
return postRequest("/api", payload);
}
private String postRequest(String endpoint, String payload) {
// Simulierter HTTP-Aufruf
return "POST " + baseUrl + endpoint + " - Body: " + payload;
}
}
// Richardson Maturity Model Level 1: Resources
class Level1Api {
private String baseUrl;
public Level1Api(String baseUrl) {
this.baseUrl = baseUrl;
}
// Eigene URIs für Ressourcen, aber nur GET
public String getUser(long id) {
// GET /api/users/123 - Korrekte URI, aber nur GET
return getRequest("/api/users/" + id);
}
public String getAllUsers() {
return getRequest("/api/users");
}
// Aber immer noch POST für alles andere
public String createUser(String name, String email) {
String payload = String.format("{\"name\": \"%s\", \"email\": \"%s\"}", name, email);
return postRequest("/api/users", payload);
}
private String getRequest(String endpoint) {
return "GET " + baseUrl + endpoint;
}
private String postRequest(String endpoint, String payload) {
return "POST " + baseUrl + endpoint + " - Body: " + payload;
}
}
// Richardson Maturity Model Level 2: HTTP Verbs
class Level2Api {
private String baseUrl;
public Level2Api(String baseUrl) {
this.baseUrl = baseUrl;
}
// Korrekte HTTP-Methoden für CRUD
public String getUser(long id) {
return "GET " + baseUrl + "/api/users/" + id;
}
public String getAllUsers() {
return "GET " + baseUrl + "/api/users";
}
public String createUser(String name, String email) {
String payload = String.format("{\"name\": \"%s\", \"email\": \"%s\"}", name, email);
return "POST " + baseUrl + "/api/users - Body: " + payload;
}
public String updateUser(long id, String name, String email) {
String payload = String.format("{\"name\": \"%s\", \"email\": \"%s\"}", name, email);
return "PUT " + baseUrl + "/api/users/" + id + " - Body: " + payload;
}
public String deleteUser(long id) {
return "DELETE " + baseUrl + "/api/users/" + id;
}
}
// Richardson Maturity Model Level 3: Hypermedia (HATEOAS)
class Level3Api {
private String baseUrl;
public Level3Api(String baseUrl) {
this.baseUrl = baseUrl;
}
// Vollständige HATEOAS-Implementierung
public Map<String, Object> getUser(long id) {
Map<String, Object> user = new HashMap<>();
user.put("id", id);
user.put("name", "Alice");
user.put("email", "alice@example.com");
// HATEOAS Links
Map<String, Object> links = new HashMap<>();
links.put("self", Map.of("href", "/api/users/" + id));
links.put("collection", Map.of("href", "/api/users"));
links.put("update", Map.of("href", "/api/users/" + id, "method", "PUT"));
links.put("delete", Map.of("href", "/api/users/" + id, "method", "DELETE"));
user.put("_links", links);
return user;
}
public Map<String, Object> getAllUsers() {
List<Map<String, Object>> users = new ArrayList<>();
// Benutzer mit Links
Map<String, Object> user1 = getUser(1L);
Map<String, Object> user2 = getUser(2L);
users.add(user1);
users.add(user2);
Map<String, Object> response = new HashMap<>();
response.put("users", users);
// Collection Links
Map<String, Object> links = new HashMap<>();
links.put("self", Map.of("href", "/api/users"));
links.put("create", Map.of("href", "/api/users", "method", "POST"));
links.put("search", Map.of("href", "/api/users/search", "method", "GET"));
response.put("_links", links);
response.put("count", users.size());
return response;
}
public Map<String, Object> createUser(String name, String email) {
// Erstellte Ressource mit Links zurückgeben
Map<String, Object> createdUser = new HashMap<>();
createdUser.put("id", 3L);
createdUser.put("name", name);
createdUser.put("email", email);
Map<String, Object> links = new HashMap<>();
links.put("self", Map.of("href", "/api/users/3"));
links.put("collection", Map.of("href", "/api/users"));
links.put("update", Map.of("href", "/api/users/3", "method", "PUT"));
links.put("delete", Map.of("href", "/api/users/3", "method", "DELETE"));
createdUser.put("_links", links);
Map<String, Object> response = new HashMap<>();
response.put("user", createdUser);
response.put("message", "Benutzer erstellt");
response.put("_links", Map.of(
"self", Map.of("href", "/api/users/3"),
"all", Map.of("href", "/api/users")
));
return response;
}
}
// Richardson Maturity Model Demo
public class RichardsonMaturityDemo {
public static void main(String[] args) {
System.out.println("=== Richardson Maturity Model Demo ===");
String baseUrl = "http://api.example.com";
// Level 0: Swamp of POX
System.out.println("\n--- Level 0: Swamp of POX ---");
Level0Api level0 = new Level0Api(baseUrl);
System.out.println("Create User: " + level0.createUser("Alice", "alice@example.com"));
System.out.println("Get User: " + level0.getUser(123));
// Level 1: Resources
System.out.println("\n--- Level 1: Resources ---");
Level1Api level1 = new Level1Api(baseUrl);
System.out.println("Get User: " + level1.getUser(123));
System.out.println("Get All Users: " + level1.getAllUsers());
System.out.println("Create User: " + level1.createUser("Bob", "bob@example.com"));
// Level 2: HTTP Verbs
System.out.println("\n--- Level 2: HTTP Verbs ---");
Level2Api level2 = new Level2Api(baseUrl);
System.out.println("Get User: " + level2.getUser(123));
System.out.println("Create User: " + level2.createUser("Charlie", "charlie@example.com"));
System.out.println("Update User: " + level2.updateUser(123, "Charlie Updated", "charlie.new@example.com"));
System.out.println("Delete User: " + level2.deleteUser(123));
// Level 3: Hypermedia (HATEOAS)
System.out.println("\n--- Level 3: Hypermedia (HATEOAS) ---");
Level3Api level3 = new Level3Api(baseUrl);
Map<String, Object> userResponse = level3.getUser(123);
System.out.println("Get User with HATEOAS:");
printJson(userResponse);
Map<String, Object> allUsersResponse = level3.getAllUsers();
System.out.println("\nAll Users with HATEOAS:");
printJson(allUsersResponse);
Map<String, Object> createResponse = level3.createUser("David", "david@example.com");
System.out.println("\nCreate User with HATEOAS:");
printJson(createResponse);
// Richardson Maturity Analysis
System.out.println("\n=== Richardson Maturity Analysis ===");
analyzeMaturityLevel();
}
private static void printJson(Map<String, Object> data) {
System.out.println(jsonToString(data, 0));
}
private static String jsonToString(Object obj, int indent) {
if (obj instanceof Map) {
StringBuilder sb = new StringBuilder();
Map<?, ?> map = (Map<?, ?>) obj;
String indentStr = " ".repeat(indent);
sb.append("{\n");
for (Map.Entry<?, ?> entry : map.entrySet()) {
sb.append(indentStr).append("\"").append(entry.getKey()).append("\": ");
sb.append(jsonToString(entry.getValue(), indent + 1));
sb.append(",\n");
}
if (!map.isEmpty()) {
sb.setLength(sb.length() - 2); // Remove last comma and newline
sb.append("\n");
}
sb.append(" ".repeat(indent - 1)).append("}");
return sb.toString();
} else if (obj instanceof List) {
StringBuilder sb = new StringBuilder();
List<?> list = (List<?>) obj;
String indentStr = " ".repeat(indent);
sb.append("[\n");
for (Object item : list) {
sb.append(indentStr).append(jsonToString(item, indent + 1));
sb.append(",\n");
}
if (!list.isEmpty()) {
sb.setLength(sb.length() - 2);
sb.append("\n");
}
sb.append(" ".repeat(indent - 1)).append("]");
return sb.toString();
} else {
return "\"" + obj + "\"";
}
}
private static void analyzeMaturityLevel() {
System.out.println("Richardson Maturity Model Levels:");
System.out.println("Level 0 - Swamp of POX: Nur HTTP, keine REST-Konventionen");
System.out.println("Level 1 - Resources: Eigene URIs, aber nur GET");
System.out.println("Level 2 - HTTP Verbs: Korrekte HTTP-Methoden");
System.out.println("Level 3 - Hypermedia: HATEOAS für discoverable APIs");
System.out.println("\nVorteile höherer Levels:");
System.out.println("- Bessere Cachebarkeit");
System.out.println("- Klarere Semantik");
System.out.println("- Entkopplung von Client und Server");
System.out.println("- Self-describing APIs");
}
}
3. JavaScript Client für REST API
// REST API Client mit HATEOAS-Unterstützung
class RestClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
// Universelle Request-Methode
async request(method, endpoint, data = null) {
const config = {
method: method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
if (data) {
config.body = JSON.stringify(data);
}
try {
const response = await fetch(this.baseUrl + endpoint, config);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
// CRUD-Operationen
async getAllUsers() {
return this.request('GET', '/users');
}
async getUser(id) {
return this.request('GET', `/users/${id}`);
}
async createUser(userData) {
return this.request('POST', '/users', userData);
}
async updateUser(id, userData) {
return this.request('PUT', `/users/${id}`, userData);
}
async partialUpdateUser(id, updates) {
return this.request('PATCH', `/users/${id}`, updates);
}
async deleteUser(id) {
return this.request('DELETE', `/users/${id}`);
}
// HATEOAS-Navigation
async followLink(resource, linkName) {
const links = resource._links || {};
const link = links[linkName];
if (!link) {
throw new Error(`Link '${linkName}' nicht gefunden`);
}
const href = link.href;
const method = link.method || 'GET';
// Absolute URLs behandeln
const url = href.startsWith('http') ? href : this.baseUrl + href;
const config = {
method: method,
headers: {
'Accept': 'application/json'
}
};
const response = await fetch(url, config);
return response.json();
}
// Discoverable API-Client
async discoverApi() {
try {
const root = await this.request('GET', '/');
console.log('API-Entdeckung:', root);
return root;
} catch (error) {
console.warn('API-Entdeckung fehlgeschlagen:', error);
return null;
}
}
}
// HATEOAS-fähiger Client
class HateoasClient {
constructor(baseUrl) {
this.restClient = new RestClient(baseUrl);
this.cache = new Map();
}
async getUserWithNavigation(id) {
const user = await this.restClient.getUser(id);
console.log('Benutzer:', user);
// Verfügbare Aktionen anzeigen
if (user._links) {
console.log('Verfügbare Aktionen:');
Object.keys(user._links).forEach(linkName => {
const link = user._links[linkName];
console.log(` ${linkName}: ${link.href} (${link.method || 'GET'})`);
});
}
return user;
}
async navigateToCollection(resource) {
try {
const collection = await this.restClient.followLink(resource, 'collection');
console.log('Collection:', collection);
return collection;
} catch (error) {
console.error('Navigation zur Collection fehlgeschlagen:', error);
return null;
}
}
async performAction(resource, actionName, data = null) {
try {
const link = resource._links[actionName];
if (!link) {
throw new Error(`Aktion '${actionName}' nicht verfügbar`);
}
const method = link.method || 'POST';
const endpoint = link.href.replace(this.restClient.baseUrl, '');
return await this.restClient.request(method, endpoint, data);
} catch (error) {
console.error(`Aktion '${actionName}' fehlgeschlagen:`, error);
throw error;
}
}
}
// REST API Demo
async function restApiDemo() {
console.log('=== REST API Client Demo ===');
const client = new RestClient('http://localhost:8080/api');
const hateoasClient = new HateoasClient('http://localhost:8080/api');
try {
// API-Entdeckung
console.log('\n--- API-Entdeckung ---');
const apiInfo = await client.discoverApi();
// Alle Benutzer abrufen
console.log('\n--- Alle Benutzer ---');
const users = await client.getAllUsers();
console.log('Benutzer:', users);
if (users.users && users.users.length > 0) {
const firstUser = users.users[0];
// Benutzer mit HATEOAS-Navigation
console.log('\n--- Benutzer mit HATEOAS ---');
const userWithNav = await hateoasClient.getUserWithNavigation(firstUser.id);
// Zur Collection navigieren
console.log('\n--- Navigation zur Collection ---');
const collection = await hateoasClient.navigateToCollection(userWithNav);
// Benutzer aktualisieren
console.log('\n--- Benutzer aktualisieren ---');
const updatedUser = await client.updateUser(firstUser.id, {
name: 'Updated Name',
email: 'updated@example.com'
});
console.log('Aktualisierter Benutzer:', updatedUser);
}
// Neuen Benutzer erstellen
console.log('\n--- Benutzer erstellen ---');
const newUser = await client.createUser({
name: 'New User',
email: 'newuser@example.com'
});
console.log('Neuer Benutzer:', newUser);
// HATEOAS-Aktionen ausführen
if (newUser.user && newUser.user._links) {
console.log('\n--- HATEOAS-Aktionen ---');
// Self-Link folgen
const selfUser = await hateoasClient.performAction(newUser.user, 'self');
console.log('Self-Link Ergebnis:', selfUser);
// Collection-Link folgen
const collection = await hateoasClient.performAction(newUser.user, 'collection');
console.log('Collection-Link Ergebnis:', collection);
}
} catch (error) {
console.error('Demo fehlgeschlagen:', error);
}
}
// Error Handling und Retry-Mechanismus
class RobustRestClient extends RestClient {
constructor(baseUrl, maxRetries = 3) {
super(baseUrl);
this.maxRetries = maxRetries;
}
async requestWithRetry(method, endpoint, data = null) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await this.request(method, endpoint, data);
} catch (error) {
lastError = error;
// Retry nur bei Netzwerkfehlern oder 5xx
if (!this.shouldRetry(error)) {
throw error;
}
const delay = Math.pow(2, attempt) * 1000; // Exponential Backoff
console.warn(`Versuch ${attempt} fehlgeschlagen, retry in ${delay}ms:`, error.message);
if (attempt < this.maxRetries) {
await this.sleep(delay);
}
}
}
throw lastError;
}
shouldRetry(error) {
// Retry bei Netzwerkfehlern oder 5xx Statuscodes
return error.message.includes('fetch') ||
error.message.startsWith('HTTP 5');
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Demo ausführen
if (typeof module !== 'undefined' && module.exports) {
module.exports = { RestClient, HateoasClient, RobustRestClient, restApiDemo };
} else {
// Browser-Umgebung
restApiDemo();
}
HTTP-Methoden Übersicht
| Methode | Zweck | Idempotent | Safe | Beispiele |
|---|---|---|---|---|
| GET | Ressource abrufen | Ja | Ja | GET /users |
| POST | Ressource erstellen | Nein | Nein | POST /users |
| PUT | Ressource ersetzen | Ja | Nein | PUT /users/123 |
| PATCH | Ressource aktualisieren | Nein | Nein | PATCH /users/123 |
| DELETE | Ressource löschen | Ja | Nein | DELETE /users/123 |
HTTP-Statuscodes Übersicht
2xx Success
- 200 OK: Anfrage erfolgreich
- 201 Created: Ressource erstellt
- 204 No Content: Erfolgreich, aber keine Rückgabe
3xx Redirection
- 301 Moved Permanently: Permanente Weiterleitung
- 302 Found: Temporäre Weiterleitung
- 304 Not Modified: Nicht verändert (Cache)
4xx Client Error
- 400 Bad Request: Ungültige Anfrage
- 401 Unauthorized: Authentifizierung erforderlich
- 403 Forbidden: Zugriff verweigert
- 404 Not Found: Ressource nicht gefunden
- 422 Unprocessable Entity: Validierungsfehler
5xx Server Error
- 500 Internal Server Error: Serverfehler
- 502 Bad Gateway: Gateway-Fehler
- 503 Service Unavailable: Dienst nicht verfügbar
Richardson Maturity Model
Level 0: Swamp of POX
- Nur HTTP als Transportprotokoll
- Keine REST-Konventionen
- Nur POST für alles
Level 1: Resources
- Eigene URIs für Ressourcen
- Aber nur GET-Methode
- Keine korrekten HTTP-Methoden
Level 2: HTTP Verbs
- Korrekte HTTP-Methoden
- CRUD-Operationen
- Statuscodes richtig genutzt
Level 3: Hypermedia (HATEOAS)
- Alle vorherigen Level
- Hypermedia-Links
- Self-describing APIs
HATEOAS Best Practices
Link-Struktur
{
"_links": {
"self": {
"href": "/api/users/123"
},
"collection": {
"href": "/api/users"
},
"update": {
"href": "/api/users/123",
"method": "PUT"
},
"delete": {
"href": "/api/users/123",
"method": "DELETE"
}
}
}
Embedded Resources
{
"user": {
"id": 123,
"name": "Alice",
"_embedded": {
"orders": [
{
"id": 456,
"total": 99.99
}
]
}
}
}
REST API Design Guidelines
URI-Design
- Nomen: Ressourcen als Nomen (/users, /products)
- Plural: Collections im Plural (/users nicht /user)
- Hierarchisch: Logische Struktur (/users/123/orders)
- Kleinbuchstaben: Konsistente Kleinschreibung
Request/Response
- JSON: Standard-Format
- Consistent: Einheitliche Struktur
- Versioning: API-Versionierung (/api/v1/users)
- Pagination: Große Datensätze aufteilen
Security
- HTTPS: Verschlüsselte Verbindung
- Authentication: JWT, OAuth 2.0
- Authorization: Rollenbasierte Rechte
- Rate Limiting: Missbrauch verhindern
Vorteile und Nachteile
Vorteile von REST
- Scalability: Stateless Architektur
- Flexibility: Plattformunabhängig
- Cacheability: HTTP-Caching nutzen
- Simplicity: Einfache Konzepte
- Standardization: HTTP-Standard
Nachteile
- Overhead: HTTP-Header overhead
- Statelessness: Manuelle Zustandsverwaltung
- Complexity: HATEOAS kann komplex werden
- Versioning: API-Versionierung herausfordernd
Häufige Prüfungsfragen
-
Was ist der Unterschied zwischen PUT und PATCH? PUT ersetzt die gesamte Ressource, PATCH aktualisiert nur Teile.
-
Erklären Sie HATEOAS! Hypermedia as the Engine of Application State - APIs sind durch Hyperlinks selbstentdeckend.
-
Was bedeutet idempotent bei HTTP-Methoden? Mehrfachaufrufe haben die gleiche Wirkung wie ein einzelner Aufruf.
-
Welche Level gibt es im Richardson Maturity Model? Level 0 (POX), Level 1 (Resources), Level 2 (HTTP Verbs), Level 3 (Hypermedia).
Wichtigste Quellen
- https://restfulapi.net/
- https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
- https://martinfowler.com/articles/richardsonMaturityModel.html