Spring Boot Topic

Best Practices for REST API Design

Designing a REST API that is intuitive, maintainable, and scalable requires following established conventions and principles. Here's a comprehensive guide:

1. RESTful Principles & Naming Conventions

Use Nouns for Resources

# Good
GET /users
GET /users/123
POST /users
PUT /users/123
DELETE /users/123

# Avoid
GET /getUsers
POST /createUser
GET /getUserById/123

Use Plural Nouns

# Good
GET /users
GET /users/123/orders
GET /users/123/orders/456/items

# Avoid
GET /user
GET /user/123/order

Use HTTP Methods Properly

Method Description Idempotent Safe
GET Retrieve resources Yes Yes
POST Create new resource No No
PUT Update/replace resource Yes No
PATCH Partial update No No
DELETE Delete resource Yes No

2. API Versioning

URL Versioning (Most Common)

GET /api/v1/users
GET /api/v2/users

Header Versioning

GET /api/users
Accept: application/vnd.company.v1+json

Best Practices

  • Version from the start

  • Maintain backward compatibility

  • Document deprecation timelines

3. Request & Response Design

Filtering, Sorting, Pagination

# Filtering
GET /users?role=admin&status=active

# Sorting
GET /users?sort=name,-created_at  # - for descending

# Pagination
GET /users?page=2&limit=20

# Field selection
GET /users?fields=id,name,email
Search Endpoints
# Use /search for complex queries
GET /users/search?q=john&location=ny
POST /users/search  # For complex search criteria

4. HTTP Status Codes

Success Codes

  • 200 OK - Successful GET, PUT, PATCH

  • 201 Created - Successful POST

  • 202 Accepted - Request accepted for processing

  • 204 No Content - Successful DELETE

Client Error Codes

  • 400 Bad Request - Malformed request

  • 401 Unauthorized - Authentication needed

  • 403 Forbidden - Authenticated but no permission

  • 404 Not Found - Resource doesn't exist

  • 409 Conflict - Resource conflict

Server Error Codes

  • 500 Internal Server Error - Generic server error

  • 502 Bad Gateway - Invalid response from upstream

  • 503 Service Unavailable - Temporary overload

5. Response Format

Consistent Response Wrapper

{
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00Z",
    "version": "1.0"
  }
}

Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "details": [
      {
        "field": "email",
        "message": "Email cannot be blank"
      }
    ],
    "timestamp": "2024-01-15T10:30:00Z",
    "traceId": "abc-123-xyz"
  }
}

Pagination Response

{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 150,
    "pages": 8
  },
  "links": {
    "first": "/users?page=1&limit=20",
    "prev": "/users?page=1&limit=20",
    "next": "/users?page=3&limit=20",
    "last": "/users?page=8&limit=20"
  }
}

6. Security Best Practices

Authentication & Authorization

# Use Bearer tokens
Authorization: Bearer <jwt-token>

# API Keys for server-to-server
X-API-Key: <api-key>

Security Headers

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

Rate Limiting

# Response headers for rate limiting
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642230000
Retry-After: 60

7. Data Validation

Request Validation

// Example using Java Validation API
public class CreateUserRequest {
    @NotBlank
    @Email
    private String email;
    
    @Size(min = 8, max = 100)
    private String password;
    
    @NotNull
    private UserRole role;
}

Validation Error Response

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address"
      },
      {
        "field": "password", 
        "message": "Password must be at least 8 characters"
      }
    ]
  }
}

8. API Documentation

OpenAPI/Swagger Specification

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: Get all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'

9. Performance Optimization

Caching Headers

# Cache control
Cache-Control: public, max-age=300
ETag: "abc123"
Last-Modified: Mon, 15 Jan 2024 10:30:00 GMT

# Conditional requests
If-None-Match: "abc123"
If-Modified-Since: Mon, 15 Jan 2024 10:30:00 GMT

Compression

Accept-Encoding: gzip, deflate
Content-Encoding: gzip

Partial Responses (GraphQL-like)

GET /users/123?fields=id,name,email
GET /users/123?exclude=password,ssn

10. Code Structure & Organization

Controller Layer (Spring Boot Example)


@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
    
    @GetMapping
    public ResponseEntity<PageResponse<UserResponse>> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @RequestParam(required = false) String sort) {
        // Implementation
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
        // Implementation
    }
    
    @PostMapping
    public ResponseEntity<UserResponse> createUser(
            @Valid @RequestBody CreateUserRequest request) {
        // Implementation
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        // Implementation
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        // Implementation
        return ResponseEntity.noContent().build();
    }
}

Request/Response DTOs

public class CreateUserRequest {
    @NotBlank
    private String name;
    
    @Email
    private String email;
    
    // Getters and setters
}

public class UserResponse {
    private Long id;
    private String name;
    private String email;
    private Instant createdAt;
    
    // Getters and setters
}

11. Testing Best Practices

Integration Tests


@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
    
    @Test
    void shouldCreateUser() {
        // Given
        CreateUserRequest request = new CreateUserRequest("John", "john@test.com");
        
        // When
        var response = restTemplate.postForEntity("/api/v1/users", request, UserResponse.class);
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().getId()).isNotNull();
    }
}

12. Monitoring & Logging

Structured Logging

@RestController
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    
    @GetMapping("/{id}")
    public UserResponse getUser(@PathVariable Long id) {
        logger.info("Get user request", 
            kv("userId", id),
            kv("clientIp", getClientIp()));
        
        // Implementation
    }
}

Health Check Endpoint

GET /health
{
  "status": "UP",
  "components": {
    "database": {
      "status": "UP",
      "details": {
        "responseTime": 45
      }
    },
    "diskSpace": {
      "status": "UP"
    }
  }
}

13. Common Anti-Patterns to Avoid

Don't Do This

# Using verbs in URLs
POST /getUsers
GET /createUser

# Returning plain errors
HTTP 500 - Internal Server Error

# Inconsistent naming
GET /users
GET /userOrders

# No versioning
GET /api/users

# Returning HTML errors for API
<html><body>Error 500</body></html>

In short

  • Use nouns for resources, verbs for HTTP methods

  • Implement proper HTTP status codes

  • Version your API from the start

  • Use consistent error response format

  • Implement authentication and authorization

  • Add rate limiting and throttling

  • Provide comprehensive documentation

  • Implement proper caching headers

  • Use pagination for large datasets

  • Validate all input data

  • Log appropriately for monitoring

  • Provide health check endpoints

  • Use HTTPS exclusively

  • Implement proper CORS policies

Following these best practices will result in a REST API that is secure, scalable, maintainable, and developer-friendly.

 

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?