1990년대 후반, 웹 애플리케이션의 급격한 확장으로 성능 저하, 확장 한계, 유지보수 문제가 부각됨
당시 주류였던 RPC·SOAP 기반 분산 시스템은 복잡성과 높은 결합도로 인해 변화와 확장이 어려웠다.
이에 단순하고 표준화된 설계 철학을 통해 다양한 플랫폼 간 상호운용성을 확보할 필요가 제기되었다.


이러한 웹 확장성의 문제에 대해 Roy Fielding은 문제 의식을 갖고, 웹에서 확장 가능한 아키텍처 설계의 필요성을 강조하였다.
Roy Fielding은 REST를 HTTP 프로토콜과 웹 아키텍처 개선의 기준으로 활용하며, 기존 HTTP/1.x 디자인에서 다음과 같은 핵심 성공 요인을 도출하고자 했다.
| 성공 요인 | 설명 |
|---|---|
| 확장성 지원 | REST의 제약을 통해 클라이언트·서버를 독립적으로 배포하고, 중간 계층(프록시·게이트웨이)과 캐시를 효과적으로 활용하여 대규모 분산 환경에서도 성능과 확장성을 유지할 수 있도록 함 |
| 표준 활용과 점진적 개선 | HTTP, URI, HTML 등 기존 웹 표준을 기반으로 하되, REST를 활용하여 HTTP/1.0 및 HTTP/1.1의 설계와 확장 방식을 정립하는 기준으로 삼음 |
| 자기서술적 메시지 설계 | 메시지가 자체적으로 의미를 포함하여 프록시·캐시·게이트웨이 등 중간자 |
Roy Fielding은 웹이 단순한 문서 공유를 넘어 다양한 서비스와 애플리케이션의 기반이 되기 위해서는, 장기적으로 확장 가능하고 표준화된 아키텍처가 필요하다고 보았다.
그 원리를 일반화하여 REST(Representational State Transfer)라는 아키텍처 스타일을 제안했다.
REST는 인터넷의 모든 대상을 ‘자원’으로 보고, 이를 식별하는 URI와 표준 HTTP 메서드를 통해 접근하도록 설계하여, 단순함과 상호운용성을 동시에 달성하는 것을 목표로 한다.
자원(RESOURCE) - URI, URL ex) /blog/31 /animal/dog/42 /animal/cat?name=페르시안
행위(Verb) - HTTP METHOD (GET, POST, PUT, PATCH, DELETE)
표현(Representations)– JSON, XML, HTML

REST의 제약조건은 Roy Fielding가 제시한 REST를 달성하기 위한 최소한의 제약조건 이며, 클라이언트-서버 구조, 무상태성, 캐시 처리, 계층화 시스템, 일관된 인터페이스 등과 같은 설계 규칙을 말한다.
| 제약조건 | 개념 설명 | 효과 |
|---|---|---|
| 클라이언트-서버 구조 (Client-Server) | 클라이언트와 서버의 역할을 명확히 분리하여 독립적으로 개발·배포 가능하게 함 | 확장성 향상, 역할 분리로 유지보수 용이 |
| 무상태성 (Stateless) | 각 요청이 독립적으로 처리되며, 서버는 클라이언트의 상태를 저장하지 않음 | 서버 확장 용이, 장애 복구 용이 |
| 캐시 가능성 (Cacheable) | 응답이 명시적으로 캐시 가능 여부를 포함해야 함 | 네트워크 부하 감소, 성능 향상 |
| 일관된 인터페이스 (Uniform Interface) | 리소스 식별, 표현 전송, 자기서술적 메시지, HATEOAS 등으로 인터페이스를 일관되게 유지 | 학습 곡선 완화, 상호 운용성 증가 |
| 계층화 시스템 (Layered System) | 클라이언트는 중간 계층(프록시, 게이트웨이 등)을 의식하지 않고 요청 가능 | 보안, 로드밸런싱, 확장성 강화 |
| 코드 온 디맨드 (Code on Demand) (선택 사항– JSON 활용) | 서버가 클라이언트로 실행 가능한 코드(예: JavaScript)를 전송 가능 → JSON으로 활용되게 된 원인 | 클라이언트 기능 확장, 유연성 제공 |
Richardson 성숙도 모델은 RESTful API 성숙도를 4단계로 구분해 API가 REST 원칙을 얼마나 충실히 따르고 있는지 평가하는 모델이다. 레벨 0부터 3까지 점진적으로 자원 중심 설계, HTTP 메서드 활용, HATEOAS 적용이 강화되며, 각 단계가 높아질수록 REST 아키텍처 제약조건을 더 잘 반영한다.

REST 설계에서 이상적인 REST 제약조건을 모두 지키기 어려운 것이 현실이다.
레거시 API, 조직의 기술 스택, 유지보수 비용 등을 고려해 제약조건을 부분적으로 완화하거나 변형하는 것이 일반적이다. 이 과정에서 확장성, 단순성, 호환성 등 서로 충돌하는 요구사항 간의 균형을 찾아야 하며, 즉, API 설계 시에는 장기적인 유지보수성과 현재의 개발·운영 효율 사이에서 합리적인 트레이드오프를 결정하는 것이 핵심이다.
Open API(Open Application Programming Interface)란 누구나 접근할 수 있도록 공개된 API로, 인증 절차나 사용 정책을 준수하면 외부 개발자가 해당 API를 활용하여 서비스나 애플리케이션을 개발할 수 있도록 제공된다.
이는 조직 내부뿐 아니라 제3자 개발자와도 기능·데이터를 공유하는 표준화된 인터페이스를 의미한다.
카카오 Open API는 로그인, 지도, 번역, 검색, 메시지 발송 등 다양한 카카오 플랫폼 기능을 외부 서비스에서 활용할 수 있도록 제공하는 API 모음이다. 카카오톡 친구/채팅방 메시지 전송, 카카오 지도 표시· 길찾기, 카카오 계정 기반 인증 등을 활용 할 수 있다.

GitHub API는 저장소 관리, 이슈· 풀리퀘스트 처리, 사용자· 조직 정보 조회 등 GitHub 기능을 외부 애플리케이션에서 활용할 수 있도록 제공하는 RESTful API이다. 이를 통해 코드 조회· 수정, 워크플로 자동화, 협업 관리 등을 구현할 수 있다.

Google 클라우드 API는 Google Cloud Platform의 컴퓨팅, 스토리지, 데이터 분석, AI·ML, 네트워킹 등 다양한 서비스를 프로그래밍 방식으로 제어· 활용할 수 있도록 제공하는 API이다. 이를 통해 애플리케이션에서 자원 생성·관리, 데이터 처리, AI 모델 활용 등의 기능을 구현할 수 있다.

API-First 접근 방식이란 애플리케이션 개발에서 API를 가장 먼저 설계하고 정의한 뒤, 이를 기반으로 클라이언트·서버·서비스 구현을 진행하는 방법론이다. 즉, API를 애플리케이션의 핵심 계약(contract)으로 간주하고, 비즈니스 요구사항과 데이터 흐름을 API 명세서로 먼저 확정한 후 개발 전반에 반영하는 것이다
리소스 중심 API 설계는 시스템의 기능을 동작 중심이 아닌 ‘리소스(자원)’를 중심으로 식별·표현하고, 각 리소스에 대한 행위를 HTTP 메서드로 구분하는 방식이다. 이를 통해 URI는 리소스를 나타내고, GET·POST·PUT·DELETE 등의 메서드로 상태를 조회·생성·수정·삭제할 수 있다.
/users → 사용자 목록/users/101 → id=101 사용자 정보/blogs → 블로그 글 목록/blogs/42 → id=42 블로그 글/users/101/blogs → 특정 사용자의 블로그 글 목록/users/101/blogs/42 → 특정 사용자의 특정 블로그 글/users/101/followers → id=101 사용자의 팔로워 목록/users/101/following → id=101 사용자가 팔로우하는 사용자 목록/users/101/blogs/42/comments → 특정 사용자의 블로그 글에 달린 댓글 목록/users/101/blogs/42/comments/7 → 특정 댓글(id=7) 세부 정보/blogs?category=it&sort=desc/blogs/42 → 블로그 id=42/users/101, /blogs/42/users/550e8400-e29b-41d4-a716-446655440000/blogs/rest-api-design-principles/users/101/blogs/42 (사용자와 해당 블로그 글을 함께 식별)HTTP 메서드는 웹 리소스에 대한 행위(Method)를 표준화된 방식으로 정의한 것이다.
REST의 메서드는 HTTP의 GET, POST, PUT, PATCH, DELETE 등의 메서드를 통해 조회, 생성, 수정, 삭제를 수행하는 명령으로 활용된다.
| 메서드 | 개념 | URL 예시 |
|---|---|---|
| GET | 서버에서 리소스를 조회하는 안전한 요청 서버 상태를 변경하지 않음 | /users /users/101 /blogs?category=it&page=1&size=5 |
| POST | 새로운 리소스를 생성하는 요청 요청 본문에 생성할 데이터 포함 | /users /blogs /users/101/blogs |
| PUT/PATCH | 기존 리소스를 수정하는 요청 PUT: 전체 교체, PATCH: 부분 수정 | PUT /users/101 PATCH /users/101 PATCH /blogs/42 |
| DELETE | 기존 리소스를 삭제하는 요청 | /users/101 /blogs/42 /users/101/blogs/42 |
REST에서 상태 코드는 요청 처리 결과를 표준화된 숫자와 의미로 전달하는 규약으로, 클라이언트가 응답의 의미를 이해하고 적절한 후속 조치를 취할 수 있도록 돕는다. 올바른 상태 코드 사용은 API의 가독성과 신뢰성을 높인다.
| 범위 | 의미 | 예시 코드 및 설명 |
|---|---|---|
| 2xx | 성공(Success) | 200 OK: 요청 성공 201 Created: 리소스 생성 성공 |
| 3xx | 리다이렉션(Redirection) | 301 Moved Permanently: 영구적으로 URL 변경 302 Found: 임시 리다이렉션 |
| 4xx | 클라이언트 오류(Client Error) | 400 Bad Request: 잘못된 요청 401 Unauthorized: 인증 필요 404 Not Found: 리소스 없음 |
| 5xx | 서버 오류(Server Error) | 500 Internal Server Error: 서버 내부 오류 503 Service Unavailable: 서비스 불가 |
HTTP 헤더는 요청과 응답에 부가 정보를 담아 인증, 데이터 형식, 캐싱 등 통신 방식을 제어하는 메타데이터이다. 이를 통해 REST API의 보안, 성능, 호환성을 효율적으로 관리할 수 있다.
| 구분 | 헤더 이름 | 설명 | 예시 |
|---|---|---|---|
| 요청 헤더 | Authorization | 인증 토큰 전달 | Authorization: Bearer <token> |
| 요청 헤더 | Accept | 원하는 응답 데이터 타입 지정 | Accept: application/json |
| 요청 헤더 | Content-Type | 요청 본문 데이터 타입 지정 | Content-Type: application/json |
| 응답 헤더 | Content-Type | 서버가 반환하는 데이터 타입 명시 | Content-Type: application/json |
| 응답 헤더 | Cache-Control | 캐싱 전략 설정 | Cache-Control: max-age=3600 |
| 응답 헤더 | Location | 새로 생성된 리소스 URI 명시 | Location: /users/123 |
API 응답 설계는 클라이언트가 상태와 데이터를 일관되게 이해할 수 있도록 응답 구조를 표준화하는 과정이다. 상태 코드, 메시지, 데이터 필드를 명확히 구분해 성공·실패 상황을 예측 가능하게 전달해야 한다.
POST /users HTTP/1.1
Content-Type: application/json
{
"username": "홍길동",
"email": "hong@example.com",
"role": "developer"
{
HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/101
{
"id": 101,
"username": "홍길동",
"email": "hong@example.com",
"role": "developer",
"createdAt": "2025-08-13T10:15:00Z"
}
{
"status": "success",
"message": "사용자 정보 조회 성공",
"data": {
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
}
}{
"status": "success",
"message": "블로그 글이 작성되었습니다.",
"data": {
"postId": 101,
"title": "REST API 설계 가이드"
}
}
{
"status": "error",
"message": "제목은 필수 입력 항목입니다.",
"errorCode": "POST_TITLE_REQUIRED"
}
{
"status": "error",
"message": "요청한 사용자를 찾을 수 없습니다.",
"errorCode": "USER_NOT_FOUND"
}
상태 코드: 404 Not Found@RestController는 Spring MVC에서 @Controller와 @ResponseBody를 합쳐, 메서드의 반환 값을 뷰가 아닌 HTTP 응답 본문에 직접 전송하는 어노테이션이다. 주로 JSON, XML 형태로 데이터를 반환하는 RESTful API 구현에 사용된다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users); // 200 OK
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user); // 200 OK
} else {
return ResponseEntity.notFound().build(); // 404 Not Found
}
}
}
| 구분 | 설명 | 예시 |
|---|---|---|
| 어노테이션 성격 | @Controller + @ResponseBody 결합 | @RestController |
| 응답 방식 | 반환 값을 뷰(View)가 아닌 HTTP 응답 본문에 직접 전송 | 문자열, JSON, XML 등 |
| 데이터 변환 | 반환 객체를 HttpMessageConverter를 통해 자동 직렬화 | User 객체 → JSON |
| 주 사용 용도 | RESTful API, JSON/XML 기반 서비스 응답 처리 | API 서버, 비동기 통신 |
| 뷰 리졸버 사용 여부 | 사용하지 않음 | 템플릿(HTML) 렌더링 불가 |
| 메서드 예시 | @GetMapping, @PostMapping 등과 함께 사용 | @GetMapping("/users") |
| 어노테이션 | 설명 | 활용 예시 |
|---|---|---|
@RestController | REST API 컨트롤러를 정의하며, 메서드 반환값을 자동으로 JSON/XML 형식의 HTTP 응답 본문으로 변환합니다. (=@Controller + @ResponseBody) | @RestController@RequestMapping("/users") |
@ResponseBody | 메서드의 반환값을 HTTP 응답 본문으로 직렬화하여 직접 반환합니다. (일반적으로 @RestController 사용 시 생략) | @GetMapping("/ping")@ResponseBody |
@RequestMapping | 클래스나 메서드에 URL 경로와 HTTP 메서드를 매핑하는 기본 어노테이션입니다. REST에서는 주로 클래스 레벨에서 경로 prefix 지정에 활용합니다. | @RequestMapping(value="/users", method=RequestMethod.GET) |
| 어노테이션 | 설명 | 활용 예시 |
|---|---|---|
@GetMapping | GET 요청을 처리하며, 서버에서 리소스를 조회할 때 사용합니다. | @GetMapping("/users/{id}") |
@PostMapping | POST 요청을 처리하며, 새로운 리소스를 생성할 때 사용합니다. | @PostMapping("/users") |
@PutMapping | PUT 요청을 처리하며, 기존 리소스를 전체 교체(전체 수정) 합니다. | @PutMapping("/users/{id}") |
@PatchMapping | PATCH 요청을 처리하며, 기존 리소스의 일부만 수정 합니다. | @PatchMapping("/users/{id}") |
@DeleteMapping | DELETE 요청을 처리하며, 지정한 리소스를 삭제합니다. | @DeleteMapping("/users/{id}") |
| 어노테이션 | 설명 | 활용 예시 |
|---|---|---|
@PathVariable | URL 경로의 변수를 메서드 매개변수에 매핑합니다. | getUser(@PathVariable Long id) |
@RequestParam | 쿼리 파라미터를 메서드 매개변수에 매핑합니다. | getBlogs(@RequestParam String category) |
@RequestBody | HTTP 요청 본문(JSON/XML)을 자바 객체로 변환해 매핑합니다. | create(@RequestBody User user) |
@RequestPart | Multipart 요청에서 JSON 또는 파일의 일부 파트를 받아옵니다. | upload(@RequestPart("meta") MetaData data, @RequestPart("file") MultipartFile file) |
@ModelAttribute | 요청 파라미터를 모델 객체로 바인딩합니다. | search(@ModelAttribute SearchForm form) |
@RequestHeader | HTTP 요청 헤더 값을 매개변수에 매핑합니다. | info(@RequestHeader("User-Agent") String ua) |
| 어노테이션 | 설명 | 활용 예시 |
|---|---|---|
@ResponseStatus | 메서드의 실행 결과로 반환할 HTTP 상태 코드를 지정합니다. | @ResponseStatus(HttpStatus.CREATED) |
@ExceptionHandler | 특정 예외 발생 시 처리할 메서드를 지정합니다. | @ExceptionHandler(UserNotFoundException.class) |
@RestControllerAdvice | 전역 예외 처리, 데이터 바인딩, 모델 속성 추가 등을 지원하는 전역 컨트롤러 설정입니다. | @RestControllerAdvice public class GlobalHandler {} |
| 어노테이션 | 설명 | 예시 |
|---|---|---|
@JsonProperty | JSON 필드 이름과 Java 필드 이름 매핑 | @JsonProperty("user_name")private String username; |
@JsonIgnore | 직렬화·역직렬화 대상에서 제외 | @JsonIgnoreprivate String password; |
@JsonIgnoreProperties | 여러 필드를 무시할 때 사용 | @JsonIgnoreProperties({"password", "ssn"}) |
@JsonInclude | null 값이나 특정 조건의 값 제외 | @JsonInclude(JsonInclude.Include.NON_NULL) |
@JsonFormat | 날짜/시간, 숫자 포맷 지정 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createdAt; |
ResponseEntity는 Spring에서 HTTP 응답을 세밀하게 제어할 수 있도록 제공하는 클래스이다.
이를 통해 HTTP 상태 코드, 응답 헤더, 응답 본문을 모두 설정할 수 있으며, REST API의 명확한 응답 표준화를 구현하는 데 유용하다.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
Optional<User> user = userService.findById(id);
if (user.isPresent()) {
return ResponseEntity
.status(HttpStatus.OK) // 상태 코드 설정
.header("Custom-Header", "Value") // 헤더 추가
.body(user.get()); // 본문 데이터
} else {
return ResponseEntity
.status(HttpStatus.NOT_FOUND) // 404 설정
.body(null);
}
}
HTTP 메시지 컨버터(HttpMessageConverter)는 스프링 MVC에서 HTTP 요청과 응답 바디를 자바 객체와 서로 변환하는 역할을 수행한다. 컨트롤러에서 @ResponseBody나 @RestController를 통해 반환된 객체는 클라이언트 요청 형식(JSON, XML, 문자열 등)에 맞추어 직렬화된다. 또한 클라이언트가 전송한 요청 바디는 해당 컨버터를 통해 자바 객체로 역직렬화 되어 컨트롤러의 파라미터로 전달된다.
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }@GetMapping("/blogs")
public List<Blog> getBlogs(@RequestParam String category, @RequestParam int page) { ... }@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDto userDto) { ... }@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> uploadFile(
@RequestPart("file") MultipartFile file,
@RequestPart("meta") MetaData meta
) { ... }{
"id": 42,
"title": "REST 설계 가이드",
"author": "홍길동",
"category": "IT 개발"
}{
"status": "success",
"message": "블로그 글 조회 성공"
"data": {
"id": 42,
"title": "REST API 설계 원칙",
"author": "홍길동"
}
}@Data
@AllArgsConstructor
public class ApiResponse<T> {
private String status;
private String message;
private T data;
}@Data
@AllArgsConstructor
public class Blog {
@JsonIgnore
private String internalCode; // JSON 응답에서 제외
@JsonProperty("blog_title")
private String title; // 속성명을 blog_title로 변경
@JsonInclude(JsonInclude.Include.NON_NULL)
private String category; // null이면 응답에서 제외
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt; // 날짜 포맷 지정
}
@RestController
@RequestMapping("/blogs")
public class BlogController {
@GetMapping(value = "/{id}.json", produces = MediaType.APPLICATION_JSON_VALUE)
public Blog getBlogJson(@PathVariable Long id) {
return new Blog(id, "REST API 설계 원칙", "홍길동");
}
@GetMapping(value = "/{id}.xml", produces = MediaType.APPLICATION_XML_VALUE)
public Blog getBlogXml(@PathVariable Long id) {
return new Blog(id, "REST API 설계 원칙", "홍길동");
}
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAll(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 오류 발생");
}
}@RestControllerAdvice
public class GlobalExceptionHandler {
// 리소스를 찾을 수 없는 경우
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ApiErrorResponse errorResponse = new ApiErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
"RESOURCE_NOT_FOUND"
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
// 그 외 서버 오류 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiErrorResponse> handleGeneralException(Exception ex) {
ApiErrorResponse errorResponse = new ApiErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"서버 내부 오류가 발생했습니다.",
"INTERNAL_SERVER_ERROR"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
@Getter
@AllArgsConstructor
public class ApiErrorResponse {
private int status;
private String message;
private String errorCode;
}