REST

  • REST (Representational State Transfer) is an architectural style for web services, defined by Roy Fielding in 2000.
  • The most common approach used in building systems because it provides a logical, simple, and scalable method for designing APIs
  • A REST API is stateless, uses URIs to identify resources, and uses HTTP methods to define the actions performed on those resources.

Core Principles

ConditionExplanation
Client-Server ArchitectureThe client (user interface) and the server (data storage) are kept separate. This allows them to be developed and updated independently of each other.
StatelessEvery request must contain all the information needed for the server to process it. The server doesn’t save any information about the client’s session.
CacheableResponses must define themselves as either cacheable or not. This allows clients to reuse response data to improve performance.
Layered SystemCommunication between the client and server is standardized in a way that allows intermediaries (like proxies or load balancers) to be introduced without the client or server needing to know about them.
Uniform Interface (consistency)There is a single, consistent way to interact with the server, typically using URIs (to identify resources) and standard HTTP methods (like GET, POST, PUT, DELETE). This simplifies the overall system architecture.
ScalabilityIt can be easily expanded as long as you follow the established rules
Code on Demand (Optional)The server can provide executable code (like JavaScript) to the client, extending its functionality. This is the only optional principle.

Stateless

  • The most critical characteristic of REST - the server does not remember the context of previous requests
  • Because of this, every single request must contain all the information necessary for the server to process it
  • For example, repetitive data like an access token must be included in every call:
GET /orders/42 HTTP/1.1
Authorization: Bearer eyJhbGci...
  • If you need to change the state of a resource, you must do so by sending a new request with the new state information in its URL or body.

Uniform Interface (consistency)

  • REST APIs consistently use standard HTTP methods (GET, POST, PUT, DELETE) and a resource-focused URI structure.
  • This consistency makes it easy to predict how an API will work, even if you’ve never seen it before. For example:
GET    /users        → Retrieves a list of users
GET    /users/1      → Retrieves a specific user
POST   /users        → Creates a new user
PUT    /users/1      → Updates a user's information
DELETE /users/1      → Deletes a user

Scalability

Because REST is designed around resources, it can be easily expanded as long as you follow the established rules. For instance, if you have an existing API that only provides /users, it feels completely natural to add new functionality like this:

GET  /users/1/friends         → Get a user's friend list
GET  /users/1/posts           → Get a user's posts
GET  /users/1/notifications   → Get a user's notifications
  • Even as new features are added, the API can be expanded naturally while maintaining the original URI design principles.

REST API Endpoint Design

  • 기능 단위로 무분별하게 URI를 만들기보단, 항상 “리소스 → 행위 → 파라미터” 순으로 나누어 설계하세요.

Clear Intent - centered around resources

  • URIs should focus on resources (nouns), with actions defined by the HTTP method.
  • Incorrect: GET /getUserInfo?id=1
  • Correct: GET /users/1
    Explanation: users is the resource, 1 is the ID, and the action (retrieving data) is handled by GET.

Hierarchical Structure

Use hierarchical URIs to reflect resource relationships.

  • Example: GET /users/1/orders/23 → Order #23 belongs to user #1.
  • Design Tip: Treat URIs like a logical tree structure.

Naming Conventions

  • Use Plurals: Resource names should always be plural (e.g., /users, /orders, /products).
  • Use Nouns, Not Verbs: The URI should represent the resource (a noun). The HTTP method defines the action (e.g., GET /users, not /getUsers).
  • Use Kebab-case: If a resource name contains multiple words, use kebab-case (e.g., /user-profiles) rather than snake_case, as hyphens are standard and more readable in URIs.

Splitting by Functional Unit

  • For complex actions, use endpoints for inner functionality under specific resources (하위 기능을 endpoint로 분리).
    • Example: POST /users/1/reset-password → action scoped under the user resource.
  • For filtering/searching, use query parameters, not new paths.
    • Example: GET /articles?keyword=spring&page=2 👉 REST Principle: URIs identify resources, while actions are expressed via HTTP methods, query params, or request body.

Building Request + Response

https://calm-individual-12a.notion.site/API-21fc6b709828819d9096fe5dfb408653

Tips + Summary

  • Standardizing all API responses into a common wrapper or envelope structure is highly advantageous for consistent error handling, logging, and security management.
  • You can optimize the response size by controlling whether null fields are included in the JSON output using the @JsonInclude(Include.NON_NULL) annotation.
  • If a consistent field order is important, you can enforce it with @JsonPropertyOrder({"id", "name"}), which also leads to clearer and more predictable documentation.
TopicDesign Criteria
Request StructureShould be DTO-based, with input validation and clear parameter naming.
Response StructureUse a JSON wrapper (envelope) format that separates success, data, and error fields.
Error HandlingCentralize error handling with an ExceptionHandler and use clearly defined error codes.
PaginationBase it on Spring Data standards, with a structure including content, page, and total info.

Request

DTO
  • Request data must have a clear purpose and structure. This structure should be clearly defined as a contract between the server and client using a Data Transfer Object (DTO).
public class CreateUserRequest {
    @NotBlank
    private String name;
 
    @Email
    private String email;
 
    private String nickname; // optional
}
  • Use annotations (e.g., @NotBlank, @Email) to ensure input integrity.
  • Tools like Swagger auto-generate docs based on the DTO, making the contract explicit and visible.
Filtering and Sorting Patterns
  • It is common practice to structure the parameters for a GET request in the following format:
    • GET /api/users?age=20&city=seoul&sort=name,desc&page=0&size=10
  • 💡 Tip: For complex search conditions, you can receive the parameters using @ModelAttribute or as a Map<String, String> with @RequestParam.
ParameterDescription
age=20A filter for a specific age.
city=seoulA filter for a specific region.
sort=name,descThe sorting criteria (property, direction).
page=0&size=10Pagination details (see below).

Response

Consistent Response Structure

All API responses should follow a common envelope format to ensure predictable client-side parsing.

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Alice"
  },
  "error": null,
  "timestamp": "2025-06-24T12:34:56Z"
}
{
  "success": false,
  "data": null,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "Invalid parameters provided."
  },
  "timestamp": "2025-06-24T12:34:56Z"
}
  • success: A boolean indicating if the request was successful.
  • data: The actual response body containing the requested data.
  • error: An object containing error details (like a message or code) if the request failed.
  • timestamp: The server’s timestamp for when the response was generated. This standardized structure improves reliability, both in formal documentation like Swagger and during collaboration with frontend developers.
Spring Exception Handling (🐣Spring & SpringBoot)
  • Separate HTTP status codes from custom error message bodies.
    • HTTP status codes guide the frontend/browser on response handling (e.g., 401 Unauthorized).
    • JSON error body (via a dedicated ApiErrorResponse class) delivers a consistent, human-readable message.
  • This clear separation improves frontend error handling and user communication.
@ControllerAdvice
public class GlobalExceptionHandler {
 
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ApiErrorResponse> handleNotFound(EntityNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ApiErrorResponse.of("USER_NOT_FOUND", e.getMessage()));
    }
 
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiErrorResponse> handleValidation(MethodArgumentNotValidException e) {
        return ResponseEntity.badRequest()
            .body(ApiErrorResponse.of("INVALID_INPUT", e.getBindingResult().toString()));
    }
}
Pagination pattern

Spring Data 또는 일반 API 설계 시 아래와 같은 응답 포맷을 따름:

{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ],
  "page": 0,
  "size": 2,
  "totalElements": 100,
  "totalPages": 50
}
FieldDescription
dataA list containing the actual results for the current page.
pageThe current page number (typically 0-indexed).
sizeThe number of items requested per page.
totalElementsThe total number of items available across all pages.
totalPagesThe total number of pages available.

Limitations

  • Over-fetching: This happens when the API sends back more data than the client actually needs. For example, getting a full user object when you only needed the username.
  • Under-fetching: This is the opposite problem, where an endpoint doesn’t provide enough information, forcing the client to make multiple requests to get everything it needs.
  • API Versioning: As an API evolves, managing different versions can become complicated, often resulting in messy URLs that include version numbers (e.g., /v1/, /v2/).
  • To solve these specific problems, technologies like GraphQL and gRPC were introduced.