HTTP Topic
REST API Design Best Practices
What You'll Learn:
-
Richardson Maturity Model for REST APIs
-
Resource naming conventions and URL design
-
Hypermedia (HATEOAS) and API discoverability
Detailed Explanation:
Richardson Maturity Model
Level 0 - The Swamp of POX (Plain Old XML):
-
Single POST endpoint
-
All operations via POST
-
No HTTP semantics
Level 1 - Resources:
-
Multiple URLs for different resources
-
Still mostly POST operations
Level 2 - HTTP Verbs:
-
Proper use of HTTP methods (GET, POST, PUT, DELETE)
-
HTTP status codes for responses
Level 3 - Hypermedia Controls (HATEOAS):
-
Links for resource navigation
-
Self-describing APIs
Resource Design Principles
1. Use Nouns for Resources:
# Good
GET /api/users
GET /api/users/123
POST /api/users
# Avoid
GET /api/getUser
POST /api/createUser
2. Use Plural Resource Names:
# Good
GET /api/users
GET /api/users/123/orders
# Avoid
GET /api/user
GET /api/user/123/order
3. Use Sub-resources for Relations:
GET /api/users/123/orders # User's orders
GET /api/users/123/orders/456 # Specific order for user
POST /api/users/123/orders # Create order for user
4. Use Query Parameters for Filtering:
GET /api/users?role=admin&status=active
GET /api/orders?startDate=2023-01-01&endDate=2023-01-31
GET /api/products?category=electronics&priceRange=100-500
API Versioning Strategies
1. URL Versioning:
GET /api/v1/users
GET /api/v2/users
2. Query Parameter Versioning:
GET /api/users?version=1
GET /api/users?version=2
3. Header Versioning:
GET /api/users
API-Version: 1
GET /api/users
API-Version: 2
4. Content Negotiation Versioning:
GET /api/users
Accept: application/vnd.company.v1+json
GET /api/users
Accept: application/vnd.company.v2+json
HATEOAS (Hypermedia as the Engine of Application State)
Spring HATEOAS Implementation:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
linkTo(methodOn(UserController.class).getUserOrders(id)).withRel("orders"),
linkTo(methodOn(UserController.class).updateUser(id, null)).withRel("update"),
linkTo(methodOn(UserController.class).deleteUser(id)).withRel("delete")
);
}
@GetMapping
public CollectionModel<EntityModel<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
List<EntityModel<User>> users = userService.findPage(page, size)
.stream()
.map(user -> EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel()
))
.collect(Collectors.toList());
Link selfLink = linkTo(methodOn(UserController.class).getUsers(page, size)).withSelfRel();
Link nextLink = linkTo(methodOn(UserController.class).getUsers(page + 1, size)).withRel("next");
Link prevLink = page > 0 ?
linkTo(methodOn(UserController.class).getUsers(page - 1, size)).withRel("prev") : null;
CollectionModel<EntityModel<User>> collection = CollectionModel.of(users, selfLink);
if (prevLink != null) {
collection.add(prevLink);
}
collection.add(nextLink);
return collection;
}
}
HATEOAS Response Example:
json
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"_links": {
"self": {
"href": "http://localhost:8080/api/users/123"
},
"orders": {
"href": "http://localhost:8080/api/users/123/orders"
},
"update": {
"href": "http://localhost:8080/api/users/123"
},
"delete": {
"href": "http://localhost:8080/api/users/123"
}
}
}
Pagination and Sorting
Pagination Implementation:
@GetMapping
public ResponseEntity<Page<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "name,asc") String[] sort) {
Pageable pageable = PageRequest.of(page, size, getSort(sort));
Page<User> users = userService.findAll(pageable);
return ResponseEntity.ok()
.header("X-Total-Count", String.valueOf(users.getTotalElements()))
.header("X-Total-Pages", String.valueOf(users.getTotalPages()))
.body(users);
}
private Sort getSort(String[] sort) {
if (sort.length >= 2) {
return Sort.by(new Sort.Order(
Sort.Direction.fromString(sort[1]),
sort[0]
));
}
return Sort.unsorted();
}
Pagination Response Headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-Total-Count: 150
X-Total-Pages: 8
X-Current-Page: 0
Link: </api/users?page=1&size=20>; rel="next", </api/users?page=7&size=20>; rel="last"
API Documentation with OpenAPI
SpringDoc OpenAPI Configuration:
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("User API")
.version("1.0.0")
.description("API for user management")
.contact(new Contact()
.name("API Support")
.email("support@example.com")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
Controller Documentation:
@RestController
@RequestMapping("/api/users")
@Tag(name = "Users", description = "User management API")
public class UserController {
@Operation(summary = "Get user by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "User found"),
@ApiResponse(responseCode = "404", description = "User not found")
})
@GetMapping("/{id}")
public User getUser(@Parameter(description = "User ID") @PathVariable Long id) {
return userService.findById(id);
}
@Operation(summary = "Create new user")
@PostMapping
public User createUser(@RequestBody User user) {
return userService.create(user);
}
}
Comments (0)
No comments yet. Be the first to comment!