Best Practices Phát Triển REST API với Spring Boot

Giới Thiệu
Xây dựng một REST API hoạt động là một chuyện—xây dựng một API dễ maintain, scalable, và tuân theo các tiêu chuẩn ngành là chuyện khác. Trong bài viết này, chúng ta sẽ khám phá các best practices production-ready giúp phân biệt hobby projects với enterprise-grade APIs.
Những Gì Bạn Sẽ Học
✅ RESTful design principles và resource naming conventions
✅ HTTP status codes và semantic method usage
✅ Consistent response structures và error handling patterns
✅ Performance optimization với caching, compression, và rate limiting
✅ Security best practices cho production APIs
✅ Health checks, logging, và monitoring strategies
✅ Real-world patterns: filtering, sorting, pagination, soft deletes
✅ Xây dựng E-Commerce Product API với tất cả best practices
Yêu Cầu Tiên Quyết
- Java 17+ đã cài đặt
- Spring Boot basics (xem hướng dẫn Getting Started)
- Quen thuộc với REST API concepts
- Kinh nghiệm Maven hoặc Gradle
Lưu ý: Bài viết này tập trung vào conventions và standards. Để biết chi tiết implementation về exception handling, validation, và testing, xem bài viết REST API Advanced Patterns.
1. RESTful Design Principles
Resource-Oriented Architecture
REST APIs nên model resources, không phải actions. Dùng nouns, không dùng verbs.
✅ Tốt:
GET /products # List products
GET /products/{id} # Get single product
POST /products # Create product
PUT /products/{id} # Update product
DELETE /products/{id} # Delete product❌ Không tốt:
GET /getProducts
POST /createProduct
POST /updateProduct
DELETE /deleteProductResource Naming Conventions
Quy tắc:
- Dùng plural nouns cho collections:
/users,/orders,/products - Dùng lowercase và hyphens cho readability:
/order-items,/user-profiles - Tránh file extensions:
/productskhông phải/products.json - Giữ URLs shallow (max 2-3 levels):
/users/{id}/orders/{orderId} - Dùng query parameters cho filtering:
/products?category=electronics&sort=price
HTTP Methods Semantics
Mỗi HTTP method có ý nghĩa cụ thể và expected behavior:
| Method | Mục đích | Idempotent | Safe | Request Body | Response Body |
|---|---|---|---|---|---|
| GET | Retrieve resource(s) | ✅ | ✅ | ❌ | ✅ |
| POST | Create resource | ❌ | ❌ | ✅ | ✅ |
| PUT | Replace entire resource | ✅ | ❌ | ✅ | ✅ |
| PATCH | Update partial resource | ❌ | ❌ | ✅ | ✅ |
| DELETE | Remove resource | ✅ | ❌ | ❌ | Optional |
Idempotent: Multiple identical requests có cùng effect như một single request. Safe: Không modify server state.
2. Response Standards
HTTP Status Codes Best Practices
Dùng status codes phù hợp để communicate intent rõ ràng.
Success Codes (2xx):
- 200 OK: Successful GET, PUT, PATCH, DELETE với response body
- 201 Created: Successful POST (resource created)
- 204 No Content: Successful DELETE hoặc update không có response body
Client Error Codes (4xx):
- 400 Bad Request: Invalid request body hoặc parameters
- 401 Unauthorized: Authentication required hoặc invalid
- 403 Forbidden: Authenticated nhưng không authorized
- 404 Not Found: Resource không tồn tại
- 409 Conflict: Request conflicts với current state
- 422 Unprocessable Entity: Validation errors
- 429 Too Many Requests: Rate limit exceeded
Server Error Codes (5xx):
- 500 Internal Server Error: Unexpected server error
- 503 Service Unavailable: Temporary outage
Consistent Response Structure
Option 1: Direct Response (Simple APIs)
{
"id": 123,
"name": "Laptop",
"price": 999.99,
"category": "electronics"
}Option 2: Envelope Pattern (Complex APIs)
{
"success": true,
"data": {
"content": [...],
"pagination": {
"page": 0,
"size": 20,
"totalElements": 150
}
},
"timestamp": "2026-02-04T10:30:00Z"
}3. API Design Patterns
Filtering, Sorting, và Searching
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
@GetMapping
public ResponseEntity<Page<Product>> getProducts(
@RequestParam(required = false) String category,
@RequestParam(required = false) BigDecimal minPrice,
@RequestParam(required = false) BigDecimal maxPrice,
@RequestParam(required = false) String search,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String sortDirection,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
// Implementation
}
}
// Example requests:
// GET /api/v1/products?category=electronics&minPrice=100&maxPrice=1000
// GET /api/v1/products?search=laptop&sortBy=price&sortDirection=descPATCH vs PUT
PUT: Replace entire resource (tất cả fields required)
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable Long id,
@RequestBody @Valid ProductRequest request) {
// All fields trong ProductRequest là required
Product product = productService.update(id, request);
return ResponseEntity.ok(product);
}PATCH: Update only provided fields (partial update)
@PatchMapping("/{id}")
public ResponseEntity<Product> patchProduct(
@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
// Chỉ update các fields có trong request
Product product = productService.patch(id, updates);
return ResponseEntity.ok(product);
}Soft Delete Pattern
@Entity
public class Product {
@Id
private Long id;
private String name;
@Column(nullable = false)
private Boolean deleted = false;
private LocalDateTime deletedAt;
public void softDelete() {
this.deleted = true;
this.deletedAt = LocalDateTime.now();
}
}
@RestController
public class ProductController {
// Soft delete (default behavior)
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.softDelete(id);
return ResponseEntity.noContent().build();
}
// Restore soft-deleted product
@PostMapping("/{id}/restore")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Product> restoreProduct(@PathVariable Long id) {
Product product = productService.restore(id);
return ResponseEntity.ok(product);
}
}4. Performance & Scalability
Rate Limiting với Bucket4j
Prevent API abuse và ensure fair usage.
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String apiKey = request.getHeader("X-API-Key");
if (apiKey == null) {
apiKey = request.getRemoteAddr();
}
Bucket bucket = resolveBucket(apiKey);
if (bucket.tryConsume(1)) {
return true; // Allow request
}
// Rate limit exceeded
response.setStatus(429);
response.setHeader("X-Rate-Limit-Retry-After-Seconds", "60");
response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
return false;
}
private Bucket resolveBucket(String apiKey) {
return cache.computeIfAbsent(apiKey, key -> {
// 100 requests per minute
Bandwidth limit = Bandwidth.simple(100, Duration.ofMinutes(1));
return Bucket.builder().addLimit(limit).build();
});
}
}ETags và Conditional Requests
Giảm bandwidth bằng cách gửi 304 Not Modified khi content không thay đổi.
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(
@PathVariable Long id,
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
Product product = productService.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product not found"));
String etag = "\"" + product.hashCode() + "\"";
if (etag.equals(ifNoneMatch)) {
return ResponseEntity
.status(HttpStatus.NOT_MODIFIED)
.eTag(etag)
.build();
}
return ResponseEntity.ok()
.eTag(etag)
.body(product);
}Cache-Control Headers
@GetMapping
public ResponseEntity<List<Product>> getProducts() {
List<Product> products = productService.findAll();
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic())
.body(products);
}5. Security Considerations
Input Validation và Sanitization
public class ProductRequest {
@NotBlank(message = "Name is required")
@Size(min = 3, max = 100)
@Pattern(regexp = "^[a-zA-Z0-9\\s-]+$")
private String name;
@NotNull
@DecimalMin(value = "0.01")
@DecimalMax(value = "999999.99")
private BigDecimal price;
@NotBlank
@Pattern(regexp = "^(electronics|clothing|books|home|sports)$")
private String category;
}Security Headers
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.frameOptions(frame -> frame.deny())
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000))
);
return http.build();
}
}6. Production Readiness
Health Checks với Actuator
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: when-authorized# Liveness probe
GET /actuator/health/liveness
# Readiness probe
GET /actuator/health/readinessStructured Logging
@Service
public class ProductService {
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
public Product create(ProductRequest request) {
log.info("Creating product: {}",
StructuredArguments.keyValue("productName", request.getName()));
try {
Product product = productRepository.save(Product.from(request));
log.info("Product created successfully",
StructuredArguments.keyValue("productId", product.getId()));
return product;
} catch (Exception e) {
log.error("Failed to create product",
StructuredArguments.keyValue("error", e.getMessage()), e);
throw e;
}
}
}Tổng Kết và Key Takeaways
✅ RESTful design: Dùng nouns, plural resources, proper HTTP methods
✅ HTTP status codes: 2xx success, 4xx client errors, 5xx server errors
✅ Consistent responses: Envelope pattern cho complex APIs
✅ Filtering & sorting: Dùng query parameters, support pagination
✅ PUT vs PATCH: PUT replaces, PATCH updates partially
✅ Soft deletes: Mark as deleted, giữ data cho recovery
✅ Rate limiting: Protect against abuse với Bucket4j
✅ ETags: Giảm bandwidth với conditional requests
✅ Caching: Dùng Cache-Control headers appropriately
✅ Security: Validate input, dùng security headers
✅ Monitoring: Health checks, structured logging, metrics
✅ Production-ready: Handle errors gracefully, log properly
Bước Tiếp Theo
Giờ bạn đã hiểu REST API best practices, khám phá các chủ đề liên quan:
Tiếp tục Spring Boot series:
- Advanced JPA: Query Optimization - Database performance
- Caching với Redis - Cải thiện performance
Bài Viết Liên Quan:
- REST API Advanced Patterns - Exception handling, validation, testing
- Spring Boot Learning Roadmap - Lộ trình học hoàn chỉnh
- Getting Started với Spring Boot - Foundation
Thuộc chuỗi bài Spring Boot Learning Roadmap
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.