Skip to content
IRC-Coding IRC-Coding
REST API fundamentals HTTP methods status codes HATEOAS Richardson Maturity Model Web Services

REST API Basics: HTTP Methods, Status Codes & HATEOAS

Master REST API fundamentals: HTTP methods, status codes, HATEOAS, and Richardson Maturity Model with practical examples.

S

schutzgeist

2 min read
REST API Basics: HTTP Methods, Status Codes & HATEOAS

REST API Basics: HTTP Methods, Status Codes, HATEOAS & Richardson Maturity Model

This article is a comprehensive introduction to REST API basics – including HTTP methods, status codes, HATEOAS and Richardson Maturity Model with practical examples.

In a Nutshell

REST APIs use HTTP methods for CRUD operations, standardized status codes for results, HATEOAS for discoverable APIs and follow the Richardson Maturity Model for maturity levels.

Compact Technical Description

REST (Representational State Transfer) is an architectural style for distributed systems that uses the HTTP protocol and standard methods.

HTTP Methods:

  • GET: Query resource (safe, idempotent)
  • POST: Create resource (not safe, not idempotent)
  • PUT: Update/replace resource (not safe, idempotent)
  • PATCH: Partially update resource (not safe, not idempotent)
  • DELETE: Delete resource (not safe, idempotent)

Status Codes:

  • 2xx: Successful operations (200, 201, 204)
  • 3xx: Redirects (301, 302, 304)
  • 4xx: Client errors (400, 401, 403, 404, 422)
  • 5xx: Server errors (500, 502, 503)

HATEOAS: Hypermedia as the Engine of Application State - APIs are self-discoverable through hyperlinks.

Exam-Relevant Key Points

  • REST: Architectural style for web services with HTTP
  • HTTP Methods: GET, POST, PUT, PATCH, DELETE for CRUD
  • Status Codes: Standardized response codes (2xx, 3xx, 4xx, 5xx)
  • HATEOAS: Hypermedia-based navigation between resources
  • Richardson Maturity Model: Maturity levels for REST APIs (0-3)
  • Idempotency: Multiple calls have the same effect
  • Stateless: No server-side state
  • IHK-relevant: Modern web service architecture

Core Components

  1. Resources: Identifiable entities with URIs
  2. HTTP Methods: Standardized operations
  3. Status Codes: Unified response formatting
  4. Representations: JSON, XML, HTML, etc.
  5. HATEOAS: Hypermedia navigation
  6. Statelessness: Stateless communication
  7. Cacheability: HTTP caching headers
  8. Uniform Interface: Consistent API conventions

Practical Examples

1. REST API with 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;

// Data model
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 and 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 for 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 - Get all users
    @GetMapping
    public ResponseEntity<Map<String, Object>> getAllUsers() {
        List<User> users = userRepository.findAll();
        
        // Add HATEOAS links
        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} - Get user
    @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 - Create user
    @PostMapping
    public ResponseEntity<Map<String, Object>> createUser(@RequestBody User user) {
        // Validation
        if (user.getName() == null || user.getName().trim().isEmpty()) {
            return ResponseEntity.badRequest()
                .body(Map.of("error", "Name must not be empty"));
        }
        
        if (user.getEmail() == null || !user.getEmail().contains("@")) {
            return ResponseEntity.badRequest()
                .body(Map.of("error", "Invalid email address"));
        }
        
        User savedUser = userRepository.save(user);
        addHateoasLinks(savedUser);
        
        Map<String, Object> response = new HashMap<>();
        response.put("user", savedUser);
        response.put("message", "User created successfully");
        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} - Fully update user
    @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", "User updated successfully");
        
        return ResponseEntity.ok(response);
    }
    
    // PATCH /api/users/{id} - Partially update user
    @PatchMapping("/{id}")
    public ResponseEntity<?> partialUpdateUser(@PathVariable Long id, 
                                             @RequestBody Map<String, Object> updates) {
        return userRepository.findById(id)
            .map(user -> {
                // Update only specified fields
                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", "User partially updated");
                
                return ResponseEntity.ok(response);
            })
            .orElse(ResponseEntity.notFound().build());
    }
    
    // DELETE /api/users/{id} - Delete user
    @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", "User deleted successfully");
        response.put("_links", Map.of(
            "all", Map.of("href", "/api/users")
        ));
        
        return ResponseEntity.ok(response);
    }
    
    // Add HATEOAS links
    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", "Internal server error"));
    }
}

// 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 -> {
            // Create test data
            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 data created");
        };
    }
}

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;
    }
    
    // No HTTP methods, only POST for everything
    public String createUser(String name, String email) {
        // POST /api - No REST conventions
        String payload = String.format("{\"action\": \"create\", \"name\": \"%s\", \"email\": \"%s\"}", 
                                     name, email);
        return postRequest("/api", payload);
    }
    
    public String getUser(long id) {
        // POST /api - No URI resources
        String payload = String.format("{\"action\": \"get\", \"id\": %d}", id);
        return postRequest("/api", payload);
    }
    
    private String postRequest(String endpoint, String payload) {
        // Simulated HTTP call
        return "POST " + baseUrl + endpoint + " - Body: " + payload;
    }
}

// Richardson Maturity Model Level 1: Resources
class Level1Api {
    private String baseUrl;
    
    public Level1Api(String baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    // Own URIs for resources, but only GET
    public String getUser(long id) {
        // GET /api/users/123 - Correct URI, but only GET
        return getRequest("/api/users/" + id);
    }
    
    public String getAllUsers() {
        return getRequest("/api/users");
    }
    
    // But still POST for everything else
    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;
    }
    
    // Correct HTTP methods for 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;
    }
    
    // Complete HATEOAS implementation
    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<>();
        
        // Users with 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) {
        // Return created resource with links
        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", "User created");
        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: Only HTTP, no REST conventions");
        System.out.println("Level 1 - Resources: Own URIs, but only GET");
        System.out.println("Level 2 - HTTP Verbs: Correct HTTP methods");
        System.out.println("Level 3 - Hypermedia: HATEOAS for discoverable APIs");
        
        System.out.println("\nAdvantages of higher levels:");
        System.out.println("- Better cacheability");
        System.out.println("- Clearer semantics");
        System.out.println("- Decoupling of client and server");
        System.out.println("- Self-describing APIs");
    }
}

3. JavaScript Client for REST API

// REST API Client with HATEOAS support
class RestClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    // Universal request method
    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 operations
    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}' not found`);
        }
        
        const href = link.href;
        const method = link.method || 'GET';
        
        // Handle absolute URLs
        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 discovery:', root);
            return root;
        } catch (error) {
            console.warn('API discovery failed:', error);
            return null;
        }
    }
}

// HATEOAS-capable 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('User:', user);
        
        // Show available actions
        if (user._links) {
            console.log('Available actions:');
            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 to collection failed:', error);
            return null;
        }
    }
    
    async performAction(resource, actionName, data = null) {
        try {
            const link = resource._links[actionName];
            if (!link) {
                throw new Error(`Action '${actionName}' not available`);
            }
            
            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(`Action '${actionName}' failed:`, 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 discovery
        console.log('\n--- API Discovery ---');
        const apiInfo = await client.discoverApi();
        
        // Get all users
        console.log('\n--- All Users ---');
        const users = await client.getAllUsers();
        console.log('Users:', users);
        
        if (users.users && users.users.length > 0) {
            const firstUser = users.users[0];
            
            // User with HATEOAS navigation
            console.log('\n--- User with HATEOAS ---');
            const userWithNav = await hateoasClient.getUserWithNavigation(firstUser.id);
            
            // Navigate to collection
            console.log('\n--- Navigation to Collection ---');
            const collection = await hateoasClient.navigateToCollection(userWithNav);
            
            // Update user
            console.log('\n--- Update User ---');
            const updatedUser = await client.updateUser(firstUser.id, {
                name: 'Updated Name',
                email: 'updated@example.com'
            });
            console.log('Updated user:', updatedUser);
        }
        
        // Create new user
        console.log('\n--- Create User ---');
        const newUser = await client.createUser({
            name: 'New User',
            email: 'newuser@example.com'
        });
        console.log('New user:', newUser);
        
        // Perform HATEOAS actions
        if (newUser.user && newUser.user._links) {
            console.log('\n--- HATEOAS Actions ---');
            
            // Follow self link
            const selfUser = await hateoasClient.performAction(newUser.user, 'self');
            console.log('Self link result:', selfUser);
            
            // Follow collection link
            const collection = await hateoasClient.performAction(newUser.user, 'collection');
            console.log('Collection link result:', collection);
        }
        
    } catch (error) {
        console.error('Demo failed:', error);
    }
}

// Error handling and retry mechanism
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;
                
                // Only retry on network errors or 5xx
                if (!this.shouldRetry(error)) {
                    throw error;
                }
                
                const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
                console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms:`, error.message);
                
                if (attempt < this.maxRetries) {
                    await this.sleep(delay);
                }
            }
        }
        
        throw lastError;
    }
    
    shouldRetry(error) {
        // Retry on network errors or 5xx status codes
        return error.message.includes('fetch') || 
               error.message.startsWith('HTTP 5');
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// Run demo
if (typeof module !== 'undefined' && module.exports) {
    module.exports = { RestClient, HateoasClient, RobustRestClient, restApiDemo };
} else {
    // Browser environment
    restApiDemo();
}

HTTP Methods Overview

MethodPurposeIdempotentSafeExamples
GETRetrieve resourceYesYesGET /users
POSTCreate resourceNoNoPOST /users
PUTReplace resourceYesNoPUT /users/123
PATCHUpdate resourceNoNoPATCH /users/123
DELETEDelete resourceYesNoDELETE /users/123

HTTP Status Codes Overview

2xx Success

  • 200 OK: Request successful
  • 201 Created: Resource created
  • 204 No Content: Successful, but no return

3xx Redirection

  • 301 Moved Permanently: Permanent redirect
  • 302 Found: Temporary redirect
  • 304 Not Modified: Not modified (Cache)

4xx Client Error

  • 400 Bad Request: Invalid request
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource not found
  • 422 Unprocessable Entity: Validation error

5xx Server Error

  • 500 Internal Server Error: Server error
  • 502 Bad Gateway: Gateway error
  • 503 Service Unavailable: Service unavailable

Richardson Maturity Model

Level 0: Swamp of POX

  • HTTP only as transport protocol
  • No REST conventions
  • Only POST for everything

Level 1: Resources

  • Individual URIs for resources
  • But only GET method
  • No correct HTTP methods

Level 2: HTTP Verbs

  • Correct HTTP methods
  • CRUD operations
  • Status codes used correctly

Level 3: Hypermedia (HATEOAS)

  • All previous levels
  • Hypermedia links
  • Self-describing APIs

HATEOAS Best Practices

{
  "_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

  • Nouns: Resources as nouns (/users, /products)
  • Plural: Collections in plural (/users not /user)
  • Hierarchical: Logical structure (/users/123/orders)
  • Lowercase: Consistent lowercase

Request/Response

  • JSON: Standard format
  • Consistent: Uniform structure
  • Versioning: API versioning (/api/v1/users)
  • Pagination: Split large datasets

Security

  • HTTPS: Encrypted connection
  • Authentication: JWT, OAuth 2.0
  • Authorization: Role-based permissions
  • Rate Limiting: Prevent abuse

Advantages and Disadvantages

Advantages of REST

  • Scalability: Stateless architecture
  • Flexibility: Platform independent
  • Cacheability: Use HTTP caching
  • Simplicity: Simple concepts
  • Standardization: HTTP standard

Disadvantages

  • Overhead: HTTP header overhead
  • Statelessness: Manual state management
  • Complexity: HATEOAS can be complex
  • Versioning: API versioning challenging

Common Exam Questions

  1. What is the difference between PUT and PATCH? PUT replaces the entire resource, PATCH updates only parts.

  2. Explain HATEOAS! Hypermedia as the Engine of Application State - APIs are self-discoverable through hyperlinks.

  3. What does idempotent mean for HTTP methods? Multiple calls have the same effect as a single call.

  4. What levels are there in the Richardson Maturity Model? Level 0 (POX), Level 1 (Resources), Level 2 (HTTP Verbs), Level 3 (Hypermedia).

Most Important Sources

  1. https://restfulapi.net/
  2. https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
  3. https://martinfowler.com/articles/richardsonMaturityModel.html
Back to Blog
Share:

Related Posts