Sam Baek, The Dev's Corner

🌐 REST API 섀계 원칙과 Best Practice μ™„λ²½ κ°€μ΄λ“œ

06 Nov 2025

REST APIλž€ 무엇인가


μ›Ή μ„œλΉ„μŠ€λ₯Ό λ§Œλ“€ λ•Œ κ°€μž₯ λ¨Όμ € κ³ λ―Όν•˜λŠ” 것은
β€œν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„κ°€ μ–΄λ–»κ²Œ λŒ€ν™”ν• κΉŒ?”이닀.

REST APIλŠ” 이 λŒ€ν™”μ˜ κ·œμΉ™μ΄λ‹€.
마치 μš°μ²΄κ΅­μ—μ„œ νŽΈμ§€λ₯Ό 보낼 λ•Œ
μ •ν•΄μ§„ ν˜•μ‹(μ£Όμ†Œ, μš°ν‘œ, λ‚΄μš©)이 μžˆλ“―μ΄,
REST API도 μ •ν•΄μ§„ κ·œμΉ™μœΌλ‘œ 데이터λ₯Ό μ£Όκ³ λ°›λŠ”λ‹€.

REST(Representational State Transfer)λŠ”
2000λ…„ 둜이 ν•„λ”©(Roy Fielding)이 μ œμ•ˆν•œ
μ›Ήμ˜ μž₯점을 μ΅œλŒ€ν•œ ν™œμš©ν•˜λŠ” μ•„ν‚€ν…μ²˜ μŠ€νƒ€μΌμ΄λ‹€.

μ™œ REST APIλ₯Ό λ°°μ›Œμ•Ό ν• κΉŒ?


이유 1: μ‚°μ—… ν‘œμ€€
λŒ€λΆ€λΆ„μ˜ μ›Ή μ„œλΉ„μŠ€κ°€ REST APIλ₯Ό μ‚¬μš©ν•œλ‹€.

이유 2: λ‹¨μˆœν•¨
HTTPλ₯Ό κ·ΈλŒ€λ‘œ ν™œμš©ν•˜λ―€λ‘œ 별도 ν•™μŠ΅ λΉ„μš©μ΄ 적닀.

이유 3: λͺ…ν™•μ„±
URI만 봐도 μ–΄λ–€ λ¦¬μ†ŒμŠ€μΈμ§€ μ•Œ 수 μžˆλ‹€.

이유 4: λ©΄μ ‘ ν•„μˆ˜
REST API μ„€κ³„λŠ” λ°±μ—”λ“œ λ©΄μ ‘μ˜ 단골 μ§ˆλ¬Έμ΄λ‹€.

κΈ°λ³Έ κ°œλ… μš”μ•½


🏷️ REST 6κ°€μ§€ μ œμ•½ 쑰건


1. Client-Server (ν΄λΌμ΄μ–ΈνŠΈ-μ„œλ²„)


κ°œλ…: ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„μ˜ 역할을 λͺ…ν™•νžˆ 뢄리

식당 λΉ„μœ :
μ†λ‹˜(ν΄λΌμ΄μ–ΈνŠΈ)은 주문만 ν•˜κ³ ,
μ£Όλ°©(μ„œλ²„)은 μš”λ¦¬λ§Œ ν•œλ‹€.
각자의 μ—­ν• μ—λ§Œ μ§‘μ€‘ν•œλ‹€.

2. Stateless (λ¬΄μƒνƒœ)


κ°œλ…: μ„œλ²„λŠ” ν΄λΌμ΄μ–ΈνŠΈμ˜ μƒνƒœλ₯Ό μ €μž₯ν•˜μ§€ μ•ŠμŒ

우체ꡭ λΉ„μœ :
μš°μ²΄λΆ€λŠ” 맀번 μ£Όμ†Œλ₯Ό ν™•μΈν•˜κ³  λ°°λ‹¬ν•œλ‹€.
이전에 μ–΄λ””λ‘œ λ°°λ‹¬ν–ˆλŠ”μ§€ κΈ°μ–΅ν•˜μ§€ μ•ŠλŠ”λ‹€.

3. Cacheable (μΊμ‹œ κ°€λŠ₯)


κ°œλ…: HTTP의 μΊμ‹œ κΈ°λŠ₯을 κ·ΈλŒ€λ‘œ ν™œμš©

4. Uniform Interface (μΌκ΄€λœ μΈν„°νŽ˜μ΄μŠ€)


κ°œλ…: λ¦¬μ†ŒμŠ€ 식별, HTTP λ©”μ„œλ“œ μ‚¬μš©

5. Layered System (계측화)


κ°œλ…: 쀑간에 ν”„λ‘μ‹œ, κ²Œμ΄νŠΈμ›¨μ΄ μΆ”κ°€ κ°€λŠ₯

6. Code-On-Demand (선택사항)


κ°œλ…: μ„œλ²„κ°€ ν΄λΌμ΄μ–ΈνŠΈμ— μ‹€ν–‰ μ½”λ“œ 전솑 κ°€λŠ₯

🏷️ HTTP λ©”μ„œλ“œ


λ©”μ„œλ“œ 의미 λ©±λ“±μ„± μ•ˆμ „μ„± μ‚¬μš© μ˜ˆμ‹œ
GET 쑰회 O O κ²Œμ‹œκΈ€ λͺ©λ‘ 쑰회
POST 생성 X X κ²Œμ‹œκΈ€ μž‘μ„±
PUT 전체 μˆ˜μ • O X κ²Œμ‹œκΈ€ 전체 μˆ˜μ •
PATCH λΆ€λΆ„ μˆ˜μ • X X κ²Œμ‹œκΈ€ 제λͺ©λ§Œ μˆ˜μ •
DELETE μ‚­μ œ O X κ²Œμ‹œκΈ€ μ‚­μ œ


λ©±λ“±μ„±: λ™μΌν•œ μš”μ²­μ„ μ—¬λŸ¬ 번 해도 κ²°κ³Όκ°€ κ°™μŒ
μ•ˆμ „μ„±: μ„œλ²„ μƒνƒœλ₯Ό λ³€κ²½ν•˜μ§€ μ•ŠμŒ

🏷️ HTTP μƒνƒœ μ½”λ“œ


2xx - 성곡


  • 200 OK: μš”μ²­ 성곡
  • 201 Created: 생성 성곡
  • 204 No Content: 성곡 (응닡 Body μ—†μŒ)


4xx - ν΄λΌμ΄μ–ΈνŠΈ 였λ₯˜


  • 400 Bad Request: 잘λͺ»λœ μš”μ²­
  • 401 Unauthorized: 인증 ν•„μš”
  • 403 Forbidden: κΆŒν•œ μ—†μŒ
  • 404 Not Found: λ¦¬μ†ŒμŠ€ μ—†μŒ
  • 409 Conflict: 좩돌
  • 422 Unprocessable Entity: 처리 λΆˆκ°€
  • 429 Too Many Requests: μš”μ²­ κ³Όλ‹€


5xx - μ„œλ²„ 였λ₯˜


  • 500 Internal Server Error: μ„œλ²„ 였λ₯˜
  • 502 Bad Gateway: κ²Œμ΄νŠΈμ›¨μ΄ 였λ₯˜
  • 503 Service Unavailable: μ„œλΉ„μŠ€ λΆˆκ°€


URI 섀계 원칙


🏷️ Best Practices


1. λͺ…사 μ‚¬μš©, 동사 κΈˆμ§€


βœ… Good:
GET    /api/users
POST   /api/users
GET    /api/users/123
DELETE /api/users/123

❌ Bad:
/api/getUsers
/api/createUser
/api/deleteUser


2. λ³΅μˆ˜ν˜• μ‚¬μš©


βœ… Good: /api/users
❌ Bad: /api/user


3. 계측 ꡬ쑰 ν‘œν˜„


βœ… Good:
GET /api/users/123/posts
GET /api/posts/456/comments

❌ Bad:
GET /api/posts?userId=123


4. μ†Œλ¬Έμž μ‚¬μš©, ν•˜μ΄ν”ˆμœΌλ‘œ ꡬ뢄


βœ… Good: /api/user-profiles
❌ Bad: /api/UserProfiles


μ‹€μ „ μ˜ˆμ‹œ


🏷️ Spring Boot API κ΅¬ν˜„


@RestController
@RequestMapping("/api/posts")
public class PostController {
    
    @Autowired
    private PostService postService;
    
    // κ²Œμ‹œκΈ€ λͺ©λ‘ 쑰회 (νŽ˜μ΄μ§•, 필터링, μ •λ ¬)
    @GetMapping
    public ResponseEntity<Page<PostResponse>> getPosts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size,
        @RequestParam(required = false) String status,
        @RequestParam(defaultValue = "createdAt:desc") String sort
    ) {
        Pageable pageable = PageRequest.of(page, size);
        Page<PostResponse> posts = postService.getPosts(status, pageable);
        return ResponseEntity.ok(posts);
    }
    
    // κ²Œμ‹œκΈ€ 단건 쑰회
    @GetMapping("/{id}")
    public ResponseEntity<PostResponse> getPost(@PathVariable Long id) {
        PostResponse post = postService.getPost(id);
        return ResponseEntity.ok(post);
    }
    
    // κ²Œμ‹œκΈ€ μž‘μ„±
    @PostMapping
    public ResponseEntity<PostResponse> createPost(
        @Valid @RequestBody PostCreateRequest request
    ) {
        PostResponse post = postService.createPost(request);
        
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(post.getId())
            .toUri();
        
        return ResponseEntity.created(location).body(post);
    }
    
    // κ²Œμ‹œκΈ€ μˆ˜μ •
    @PutMapping("/{id}")
    public ResponseEntity<PostResponse> updatePost(
        @PathVariable Long id,
        @Valid @RequestBody PostUpdateRequest request
    ) {
        PostResponse post = postService.updatePost(id, request);
        return ResponseEntity.ok(post);
    }
    
    // κ²Œμ‹œκΈ€ μ‚­μ œ
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        postService.deletePost(id);
        return ResponseEntity.noContent().build();
    }
}


🏷️ μ—λŸ¬ 응닡 ν‘œμ€€ν™”


@Getter
@Builder
public class ErrorResponse {
    private String timestamp;
    private int status;
    private String error;
    private String message;
    private String path;
}

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
        ResourceNotFoundException ex,
        HttpServletRequest request
    ) {
        ErrorResponse error = ErrorResponse.builder()
            .timestamp(LocalDateTime.now().toString())
            .status(404)
            .error("Not Found")
            .message(ex.getMessage())
            .path(request.getRequestURI())
            .build();
        
        return ResponseEntity.status(404).body(error);
    }
}


🏷️ API 버저닝


URI 버저닝 (μΆ”μ²œ)


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


μž₯점: λͺ…ν™•ν•˜κ³  μΊμ‹œ κ°€λŠ₯
단점: URIκ°€ 변경됨

Header 버저닝


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


🏷️ HATEOAS


{
  "id": 123,
  "title": "REST API 섀계",
  "_links": {
    "self": {
      "href": "/api/posts/123"
    },
    "comments": {
      "href": "/api/posts/123/comments"
    },
    "author": {
      "href": "/api/users/456"
    }
  }
}


μ‹€μ „ 체크리슀트


βœ… URI 섀계


  • λͺ…사 μ‚¬μš© (동사 κΈˆμ§€)
  • λ³΅μˆ˜ν˜• μ‚¬μš©
  • μ†Œλ¬Έμž, ν•˜μ΄ν”ˆ μ‚¬μš©
  • 계측 ꡬ쑰 ν‘œν˜„
  • 파일 ν™•μž₯자 제거


βœ… HTTP λ©”μ„œλ“œ


  • GET: 쑰회 (λ©±λ“±μ„± O, μ•ˆμ „μ„± O)
  • POST: 생성 (λ©±λ“±μ„± X)
  • PUT: 전체 μˆ˜μ • (λ©±λ“±μ„± O)
  • PATCH: λΆ€λΆ„ μˆ˜μ •
  • DELETE: μ‚­μ œ (λ©±λ“±μ„± O)


βœ… μƒνƒœ μ½”λ“œ


  • 2xx: 성곡 (200, 201, 204)
  • 4xx: ν΄λΌμ΄μ–ΈνŠΈ 였λ₯˜ (400, 401, 403, 404)
  • 5xx: μ„œλ²„ 였λ₯˜ (500, 502, 503)


βœ… 응닡 ν˜•μ‹


  • μΌκ΄€λœ JSON ꡬ쑰
  • μ—λŸ¬ 응닡 ν‘œμ€€ν™”
  • νŽ˜μ΄μ§• 정보 포함
  • HATEOAS 링크 (선택)


βœ… λ³΄μ•ˆ


  • HTTPS μ‚¬μš©
  • 인증/인가 (JWT, OAuth)
  • Rate Limiting
  • CORS μ„€μ •
  • μž…λ ₯ 검증


βœ… λ¬Έμ„œν™”


  • Swagger/OpenAPI
  • μ˜ˆμ‹œ μ½”λ“œ
  • μ—λŸ¬ μ½”λ“œ λͺ©λ‘
  • λ³€κ²½ 이λ ₯


μš”μ•½


REST API μ„€κ³„λŠ” 일관성과 직관성이 핡심이닀.

πŸ’Ž 핡심 포인트:

  1. λ¦¬μ†ŒμŠ€ 쀑심: URIλŠ” λͺ…μ‚¬λ‘œ, λ™μ‚¬λŠ” HTTP λ©”μ„œλ“œλ‘œ
  2. λ¬΄μƒνƒœ: λ§€ μš”μ²­μ— ν•„μš”ν•œ 정보 포함
  3. HTTP ν™œμš©: λ©”μ„œλ“œμ™€ μƒνƒœ μ½”λ“œλ₯Ό μ˜¬λ°”λ₯΄κ²Œ μ‚¬μš©
  4. 일관성: 넀이밍 κ·œμΉ™μ„ ν”„λ‘œμ νŠΈ 전체에 적용
  5. 계측 ꡬ쑰: λ¦¬μ†ŒμŠ€ κ°„ 관계λ₯Ό URI둜 ν‘œν˜„
  6. μ—λŸ¬ 처리: λͺ…ν™•ν•œ μ—λŸ¬ λ©”μ‹œμ§€μ™€ μƒνƒœ μ½”λ“œ


🎯 섀계 원칙:

βœ… Do:
- GET /api/users (λͺ©λ‘ 쑰회)
- POST /api/users (생성)
- GET /api/users/123 (단건 쑰회)
- PUT /api/users/123 (μˆ˜μ •)
- DELETE /api/users/123 (μ‚­μ œ)

❌ Don't:
- POST /api/getUserList
- GET /api/createUser
- GET /api/user/delete?id=123


πŸ“Œ μƒνƒœ μ½”λ“œ κ°€μ΄λ“œ:

  • 200: GET, PUT 성곡
  • 201: POST 성곡 (Location 헀더 포함)
  • 204: DELETE 성곡
  • 400: 잘λͺ»λœ μš”μ²­
  • 401: 인증 ν•„μš”
  • 403: κΆŒν•œ μ—†μŒ
  • 404: λ¦¬μ†ŒμŠ€ μ—†μŒ
  • 500: μ„œλ²„ 였λ₯˜


πŸš€ Best Practices:

  1. 버저닝은 URI에 λͺ…μ‹œ (/api/v1/users)
  2. νŽ˜μ΄μ§•μ€ 쿼리 νŒŒλΌλ―Έν„° (?page=1&size=20)
  3. 필터링도 쿼리 νŒŒλΌλ―Έν„° (?status=active)
  4. 정렬은 sort νŒŒλΌλ―Έν„° (?sort=createdAt:desc)
  5. μ—λŸ¬ 응닡은 ν‘œμ€€ ν˜•μ‹ μœ μ§€
  6. HATEOAS둜 λ‹€μŒ 행동 μ•ˆλ‚΄ (선택)


REST APIλŠ” λ‹¨μˆœνžˆ HTTPλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ•„λ‹ˆλΌ,
μ›Ήμ˜ μ•„ν‚€ν…μ²˜λ₯Ό μ΅œλŒ€ν•œ ν™œμš©ν•˜λŠ” 섀계 철학이닀.
λͺ…ν™•ν•˜κ³  μΌκ΄€λœ APIλŠ” 개발자 κ²½ν—˜μ„ ν–₯μƒμ‹œν‚€κ³ ,
μœ μ§€λ³΄μˆ˜ λΉ„μš©μ„ 크게 쀄여쀀닀.