π Step 1: RESTful API Design & Best Practices
π§ Series Navigation:
- βΆοΈ Backend Development Fundamentals Hub
- β Step 1: RESTful API Design & Best Practices (Current)
- βΆοΈ Step 2: URI Construction & API Response Patterns
- βΆοΈ Step 3: Maven Dependency Management
- βΆοΈ Step 4: Eureka Service Registration & Discovery
- βΆοΈ Step 5: Testing Tools & Authentication Setup
π₯ YouTube Video: Coming Soon – Subscribe to JavaiOS Channel
Understanding RESTful APIs and Fixing Common Mistakes π
This document covers key concepts of RESTful APIs, the mistakes made in API design, why they were problematic, and how they were fixed. Learn from real-world examples in the AdventureTube microservice architecture.
π― What is a RESTful API?
A RESTful API (Representational State Transfer API) is an architectural style for designing web services that follow a structured and predictable way of interacting with resources over HTTP.
ποΈ Key Principles of RESTful APIs
Principle | Description |
---|---|
Client-Server | Separates client (frontend, mobile) and server (backend, database). |
Stateless | Each request contains all needed information. The server doesn’t store session state. |
Cacheable | Responses can indicate whether they can be cached to improve performance. |
Uniform Interface | Consistent structure using HTTP methods (GET , POST , PUT , DELETE ). |
Layered System | The API can work across multiple layers (security, caching, database) transparently. |
Code on Demand (Optional) | API can return executable code (e.g., JavaScript) to run on the client side. |
π Why Should APIs Be RESTful?
- β Scalability β Stateless APIs handle more concurrent requests
- β Predictability β Standard HTTP methods make the API easy to understand
- β Maintainability β Clean, resource-based URIs make debugging easier
- β Interoperability β Any system can use RESTful APIs, regardless of technology
- β Faster Development β Consistency speeds up frontend-backend integration
β Mistake #1: Non-RESTful URI Design
π¨ Old (Incorrect) Code
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath() .path("/auth/register") .toUriString()); return ResponseEntity.created(uri).body(authService.register(request));
What’s Wrong?
π¨ Issue:
- The
Location
header in the201 Created
response points to the registration endpoint (/auth/register
) instead of the newly created user resource - The client has no way to directly access the created user
- Breaks REST principles by not linking resources properly
Incorrect Response Header
POST /register Location: http://192.168.1.112:8010/auth/register
π¨ Problem: The Location
should point to the newly created user (/users/{id}
), not the registration endpoint.
β Updated (Correct) Code
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath() .path("/users/{id}") .buildAndExpand(response.getUserId()) // Replacing {id} with the actual user ID .toUriString()); return ResponseEntity.created(uri).body(response);
β Fixes:
- The
Location
header now correctly points to the created user (/users/{id}
) - The client can fetch the user directly using
GET /users/{id}
- The API now properly links resources instead of endpoints
Correct Response Header
POST /register Location: http://192.168.1.112:8010/users/8311471e-3845-49e2-90b9-826f338efc74
β Now, the client knows where to find the new user.
β Mistake #2: Returning an Inaccessible Microservice URL
π¨ Problem: Wrong Location Header with Internal IP
Even after fixing the resource-based URL, another mistake was found: the Location header returned an internal microservice IP instead of the API Gateway URL.
Incorrect Response (Not Accessible by Client)
HTTP/1.1 201 Created Location: http://192.168.1.112:8010/users/8311471e-3845-49e2-90b9-826f338efc74
π¨ Issue:
192.168.1.112:8010
is an internal microservice IP, not accessible externally- The client can’t use this address, as the microservices are only accessible via an API Gateway
- The correct
Location
should point to the API Gateway, not the microservice
β Correct Fix: Return the API Gateway URL
String gatewayBaseUrl = "https://api.adventuretube.net"; // API Gateway URL URI uri = URI.create(gatewayBaseUrl + "/users/" + response.getUserId()); return ResponseEntity.created(uri).body(response);
β Fixes:
- Now the
Location
header points to the API Gateway (api.adventuretube.net
) instead of an internal IP - The client can actually access the created resource
- Microservices remain hidden and secured
Correct Response Header (Client-Accessible)
HTTP/1.1 201 Created Location: https://api.adventuretube.net/users/8311471e-3845-49e2-90b9-826f338efc74
β Now the client can access the resource through the API Gateway.
β Mistake #3: Action-Based Login Instead of Token-Based Authentication
π¨ Problem: Using POST /doLogin
Instead of POST /auth/tokens
π¨ Issue:
- Login was handled as an action (
POST /doLogin
) instead of treating authentication as a resource - This breaks RESTful principles because authentication should be a resource (
auth/tokens
), not an action
β Correct Fix: Token-Based Authentication
Request for Token Creation
POST /auth/tokens Content-Type: application/json { "email": "strider@adventuretube.net", "googleIdToken": "eyJhbGciOiJSUzI1NiIs...", "googleId": "12345" }
Server Response with Authentication Token
HTTP/1.1 201 Created Location: https://api.adventuretube.net/auth/tokens/abcd1234xyz { "accessToken": "eyJhbGciOiJIUzUxMiJ9...", "refreshToken": "def5678uvw...", "expiresIn": 3600, "tokenType": "Bearer" }
Using the Token for Secured Requests
GET /users/123 Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
β This follows stateless authentication and is fully RESTful.
π― REST API Design Best Practices
1. Resource-Based URLs
β Wrong (Action-Based) | β Correct (Resource-Based) |
---|---|
POST /doLogin |
POST /auth/tokens |
POST /createUser |
POST /users |
GET /getUserById/123 |
GET /users/123 |
POST /deleteUser/123 |
DELETE /users/123 |
2. HTTP Status Codes
Status Code | Usage | Example |
---|---|---|
200 OK |
Successful GET, PUT, PATCH | User data retrieved successfully |
201 Created |
Successful POST (resource created) | New user registered |
204 No Content |
Successful DELETE | User deleted successfully |
400 Bad Request |
Invalid request data | Missing required fields |
401 Unauthorized |
Authentication required | Invalid or missing token |
403 Forbidden |
Access denied | Insufficient permissions |
404 Not Found |
Resource doesn’t exist | User ID not found |
500 Internal Server Error |
Server-side error | Database connection failed |
3. HTTP Methods Usage
Method | Purpose | Idempotent | Example |
---|---|---|---|
GET |
Retrieve data | Yes | GET /users/123 |
POST |
Create new resource | No | POST /users |
PUT |
Update entire resource | Yes | PUT /users/123 |
PATCH |
Partial update | No | PATCH /users/123 |
DELETE |
Remove resource | Yes | DELETE /users/123 |
π§ Implementation Examples from AdventureTube
User Registration Endpoint
@PostMapping("/auth/register") public ResponseEntity registerUser(@RequestBody SignupRequest request) { AuthResponse response = authService.createUser(request); // Correct: Point to the created user resource URI uri = URI.create(gatewayBaseUrl + "/users/" + response.getUserId()); return ResponseEntity.created(uri).body(response); }
Token Authentication Endpoint
@PostMapping("/auth/tokens") public ResponseEntity issueToken(@RequestBody AuthRequest request) { AuthResponse response = authService.issueToken(request); // RESTful: Treat token as a resource URI uri = URI.create(gatewayBaseUrl + "/auth/tokens/" + response.getTokenId()); return ResponseEntity.created(uri).body(response); }
User Retrieval Endpoint
@GetMapping("/users/{userId}") public ResponseEntity getUser(@PathVariable String userId) { UserResponse user = userService.findById(userId); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); }
β RESTful API Checklist
URL Design β
- β Use nouns for resources, not verbs
- β
Use plural nouns for collections (
/users
) - β
Use hierarchical structure (
/users/{id}/orders
) - β Avoid deep nesting (max 2-3 levels)
HTTP Methods β
- β GET for data retrieval (no side effects)
- β POST for resource creation
- β PUT for complete resource updates
- β PATCH for partial updates
- β DELETE for resource removal
Response Design β
- β Appropriate HTTP status codes
- β Consistent JSON response structure
- β Location header for created resources
- β Meaningful error messages
Security & Gateway β
- β Use API Gateway URLs in responses
- β Hide internal microservice details
- β Implement stateless authentication
- β Use HTTPS for all communications
π Key Takeaways
- Resource-Focused Design: URIs should represent resources (
/users/{id}
) rather than actions (/doLogin
) - Gateway-Only URLs: In microservices, all returned URIs must be API Gateway addresses, not internal service addresses
- Token-Based Authentication: Authentication should be treated as a resource (
POST /auth/tokens
andDELETE /auth/tokens/{id}
for logout) - Proper Status Codes: Use appropriate HTTP status codes to communicate the result of operations clearly
- Stateless Design: Each request should contain all necessary information for processing
π₯ By following these principles, your API remains RESTful, scalable, and predictable! π
π Continue Your Backend Development Journey
π§ Next Steps in Backend Development Fundamentals:
- βΆοΈ Backend Development Fundamentals Hub
- β Step 1: RESTful API Design & Best Practices (Current)
- βΆοΈ Step 2: URI Construction & API Response Patterns
- βΆοΈ Step 3: Maven Dependency Management
- βΆοΈ Step 4: Eureka Service Registration & Discovery
- βΆοΈ Step 5: Testing Tools & Authentication Setup
Part of the AdventureTube technical blog series supporting the JavaiOS YouTube channel.