🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [53]일차
🚀53일차에는 REST API개념을 복습하고 CURL 명령어에 대해 개념을 공부하였다.
휴가로 인해 미처 배우지 못한 부분들의 중요 개념들을 짚고 넘어가고자한다.
학습 목표 : CURL 명령어에 대한 이해와 POST 방식 실습
학습 과정 : 회고를 통해 작성
Rest API
- Rest(Representational State Transfer) API 에서는 HTTP 메소드를 통해서 자원에 대한 행위를 하는지 결정한다.
- 주요 HTTP 메소드와 역할
- GET : 자원을 조회 (READ)
- POST : 새로운 자원을 생성 (CREATE)
- PUT : 기존 자원을 전체 수정 (UPDATE)
- PATCH : 기존 자원을 부분 수정 (UPDATE)
- DELETE : 자원을 삭제 (DELETE) - HTTP 메소드의 엔드포인트
GET | /users | 모든 사용자 조회 |
GET | /users/1 | ID가 1인 사용자 조회 |
POST | /users | 새로운 사용자 생성 |
PUT | /users/1 | ID가 1인 사용자 정보 전체 수정 |
PATCH | /users/1 | ID가 1인 사용자 정보 일부 수정 |
DELETE | /users/1 | ID가 1인 사용자 삭제 |
- 정리하자면
RESTful API의 핵심은 "행위(동작)"를 URI가 아니라 HTTP 메소드로 구분하는 것
RESTful 설계 : @DeleteMapping("/users/1")
RESTful 하지 못한 설계 : @PostMapping("/deleteUser/1") - Rest API 설계 시 주의점
- URI에는 동사를 사용하지 않는다
⚠️/createUser (X)
✅/users (O))
- HTTP 메소드를 적절
▶️실습 - CURL 명령어 활용한 UserController
@RestController
@RequestMapping("/api/jpa/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// 사용자 등록 (POST방식)
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user){
User savedUser = userService.saveUser(user.getEmail(), user.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
// 모든 사용자 조회 (GET방식)
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
// 특정 사용자 조회 (GET방식)
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable("id") Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
// 사용자 삭제 (DELETE)
@Transactional
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable("id") Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
- @GetMapping
public ResponseEntity<list> getUsers(){ ... } : 사용자 정보 얻기
➡️http://localhost:8080/api/users
➡️curl -X GET http://localhost:8080/api/users - @PostMapping
public ResponseEntity addUser(@RequestBody User user){ ... } : 사용자 추가
➡️http://localhost:8080/api/users
➡️curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"TESTNAME\",\"email\":\"TEST@email.com\"}" // http://localhost:8080/api/users
- @GetMapping("/{id}")
public ResponseEntity getUser(@PathVariable("id") Long id){ ... } : ID를 통해 사용자 정보 얻기
➡️http://localhost:8080/api/users/{id}
➡️curl -i -X GET http://localhost:8080/api/users/1
- @PutMapping("/{id}") // 사용자 수정
public ResponseEntity updateUser(@PathVariable("id") Long id, @RequestBody User user){ ... }
➡️http://localhost:8080/api/users/{id}
➡️curl -X PUT -H "Content-Type: application/json" // -d "{\"name\":\"TESTNAME_update\",\"email\":\"test_update@email.com\"}" http://localhost:8080/api/users/1
- @DeleteMapping("/{id}")
public ResponseEntity deleteUser(@PathVariable("id")Long id){ ... } : 사용자 삭제
➡️http://localhost:8080/api/users/{id}
➡️curl -X DELETE http://localhost:8080/api/users/1
▶️실습 - Memo 프로젝트
- 데이터베이스 대신 자바의 HashMap을 사용하여 메모 데이터를 메모리에 저장
- 따라서 MemoRepository나 MemoService등은 필요하지 않음
1) MemoRestController
@RestController
@RequestMapping("/api/memos")
public class MemoRestController {
private final Map<Long, String> memos = new HashMap<>();
private final AtomicLong counter = new AtomicLong();
@PostMapping
public ResponseEntity<Long> createMemo(@RequestBody String content){
Long id = counter.incrementAndGet();
memos.put(id, content);
return ResponseEntity.ok(id);
}
// 메모를 얻어옴
@GetMapping("/{id}")
public ResponseEntity<String> getMemo(@PathVariable Long id){
String memo = memos.get(id);
if(memo == null){
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(memo);
}
// 메모를 수정함
@PutMapping("/{id}")
public ResponseEntity<String> updateMemo(@PathVariable Long id, @RequestBody String content){
if(!memos.containsKey(id)){
return ResponseEntity.notFound().build();
}
memos.put(id, content);
return ResponseEntity.ok("메모가 성공적으로 업데이트 되었습니다.");
}
// 메모를 삭제함
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteMemo(@PathVariable Long id){
String removedMemo = memos.remove(id);
if(removedMemo == null){
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok("메모를 성공적으로 삭제하였습니다.");
}
}
- private final AtomicLong counter = new AtomicLong();
➡️AtomicLong은 id값 처럼 자동으로 증가하는 값을 처리하는데 사용 - @PostMapping
public ResponseEntity<Long> createMemo(@RequestBody String content){ ... }
➡️메모 내용을 요청 본문에서 가져옴
➡️ResponseEntity<Long> : 생성된 메모의 ID를 반환 - Long id = counter.incrementAndGet();
➡️Atomic 관련 클래스들은 스레드가 안전할 수 있도록 번호 중복을 방지하는 객체
2) MemoRestController - 개선
private final Map<Long, String> memos = new HashMap<>();
private final AtomicLong counter = new AtomicLong();
@PostMapping
public Long createMemo(@RequestBody String content){
Long id = counter.incrementAndGet();
memos.put(id,content);
return id;
}
- curl -X POST -H "Content-type: text/plain" -d "첫번째 메모" <http://localhost:8080/api/memos>
http://localhost:8080/api/memos - POST의 반환타입을 ResponseEntity 없이 Long타입으로 반환하여도 REST API 구현이 가능
3) MemoRestController - 개선
@RestController
@RequestMapping("/api/memos")
public class MemoRestController3 {
private final Map<Long, Memo> memos = new HashMap<>();
private final AtomicLong counter = new AtomicLong();
@PostMapping
public Long createMemo(@RequestBody Memo memo){
Long id = counter.incrementAndGet();
memo.setId(id);
memos.put(id,memo);
return id;
}
...
}
- private final Map<Long, Memo> memos = new HashMap<>();
➡️Map<Long, String> 처럼 저장하지 않고 Map<Long, Memo> 처럼 객체 전체를 저장할 수 있도록 개선 가능
💡이전) Map<Long, String>으로 메모 내용만 저장
💡개선) Memo 객체를 사용하여 메모의 ID와 내용 모두 관리
- curl -X POST -H "Content-type: application/json" -d "{\\"content\\":\\"첫번째 메모\\"}" <http://localhost:8080/api/memos>
@GetMapping("/{id}")
public Memo getMemo(@PathVariable("id") Long id){
return memos.getOrDefault(id,null);
}
- curl -X GET <http://localhost:8080/api/memos/1>
- Map<Long, String> 이 아닌 List<Memo> 를 반환하도록 함
➡️RESTful API에서는 보통 List 형식으로 데이터를 제공
(=JSON을 @RequestBody로 받아서 객체로 변환하는 과정)
4) MemoRestController - 개선
@RestController
@RequestMapping("/api4/memos")
public class MemoRestController4 {
private final Map<Long, Memo> memos = new HashMap<>();
private final AtomicLong counter = new AtomicLong();
@PostMapping
public ResponseEntity<Long> createMemo(@RequestBody Memo memo){
Long id = counter.incrementAndGet();
memo.setId(id);
memos.put(id,memo);
return ResponseEntity.status(201).body(id);
}
@GetMapping
public ResponseEntity<Map<Long, Memo>> getMemos(){
return ResponseEntity.ok(memos);
}
@GetMapping("/{id}")
public ResponseEntity<Memo> getMemo(@PathVariable("id") Long id){
Memo memo = memos.get(id);
if(memo == null){
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(memo);
}
@PutMapping("/{id}")
public ResponseEntity<String> updateMemo(@PathVariable("id")Long id, @RequestBody Memo memo){
if(!memos.containsKey(id)){
return ResponseEntity.status(404).body("해당 메모를 찾을 수 없습니다.");
}
memo.setId(id);
memos.put(id,memo);
return ResponseEntity.ok("메모 수정에 성공하였습니다.");
}
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteMemo(@PathVariable("id")Long id){
if(memos.remove(id) == null){
return ResponseEntity.status(404).body("해당 메모를 찾을 수 없습니다.");
}
return ResponseEntity.ok("메모 삭제에 성공하였습니다.");
}
}
- 반환(=응답)에 ResponseEntity로 변경
➡️ 200 OK, 201 Created, 404 Not Found 등 적절한 상태 코드 사용 💡 HTTP 상태 코드와 함께 반환하는 것
이처럼 POST 메소드의 응답 방식을 적절하게 사용 가능 - 에러 처리 방식에 ResponseEntity 적용
➡️ResponseEntity.notFound().build(); - 삭제 완료 시 ResponseEntity 적용
➡️ResponseEntity.ok("메모 삭제에 성공하였습니다.");
❓URL과 URI의 차이점
➡️URI (Uniform Resource Identifier) : 리소스(자원)를 식별하는 모든 문자열
💡이 리소스가 무엇인지를 식별
ex. "users/1234", "<mailto:abc@example.com>"
➡️URL (Uniform Resource Locator) : 리소스의 위치(주소)와 접근 방법(프로토콜)를 포함하는 URI
💡이 리소스가 어디에 있고, 어떻게 접근하는지 판단
ex. "<https://example.com/users/1234>"
정리하자면
URL은 URI의 한 종류
모든 URL은 URI이지만 (O)
모든 URI가 URL은 아니다 (X)
curl 다양한 명령어 (+Memo 프로젝트)
- curl : HTTP 요청을 보낼 때 많이 사용하는 명령어 기반 도구
활용 : REST API 테스트 및 디버깅
명령어 | 설명 | 예시 |
-X | HTTP 메소드 지정 (GET, POST, PUT, DELETE 등) | curl -X POST <http://localhost:8080/api/memos> |
-H | 요청 헤더 설정 (Content-Type, Authorization 등) | curl -H "Content-Type: application/json" |
-d | 요청 바디 데이터 전송 (POST, PUT 요청에서 사용) | curl -d '{"content":"새로운 메모"}' |
-i | 응답 헤더 포함하여 출력 | curl -i <http://localhost:8080/api/memos> |
-v | 상세 요청/응답 로그 출력 (디버깅용) | curl -v <http://localhost:8080/api/memos> |
-s | 응답 출력 시 진행 상황 숨김 (조용히 실행) | curl -s <http://localhost:8080/api/memos> |
-o | 응답 데이터를 파일로 저장 | curl -o response.json <http://localhost:8080/api/memos> |
-u | 기본 인증 (username:password) | curl -u admin:1234 <http://localhost:8080/api/memos> |
-L | 리디렉션 자동 따라가기 | curl -L <http://example.com> |
-F | 파일 업로드 | curl -F "file=@example.txt" <http://localhost:8080/upload> |
🚀실습 - CURL 명령어 + Memo 프로젝트
GET 요청 (모든 메모 조회)
curl -X GET <http://localhost:8080/api/memos>
- 서버에서 모든 메모 리스트를 가져옴
- curl -i -X GET <...> : -i 옵션을 추가하면 응답 헤더도 함께 확인 가능
POST 요청 (새로운 메모 추가)
curl -X POST -H "Content-Type: application/json" \\
-d '{"content":"새로운 메모"}' \\
<http://localhost:8080/api/memos>
- X POST : POST 메소드로 요청
- H "Content-Type: application/json" : JSON 형식 데이터 전송
- d '{"content":"새로운 메모"}' : 요청 바디에 데이터 포함
- curl -i -X POST -H "..." : -i를 추가하면 201 Created 상태 코드 확인 가능
GET 요청 (특정 메모 조회)
curl -X GET <http://localhost:8080/api/memos/1>
- ID가 1인 메모를 조회
- curl -i -X GET <...> : ID가 1인 메모가 존재하지 않으면 404 Not Found 응답 발생
PUT 요청 (메모 수정)
curl -X PUT -H "Content-Type: application/json" \\
-d '{"content":"수정된 메모"}' \\
<http://localhost:8080/api/memos/1>
- PUT 요청으로 ID가 1인 메모 내용을 수정
- curl -i -X PUT -H "..." : 업데이트가 성공적으로 수행되면 200 OK 상태 코드 확인 가능
DELETE 요청 (메모 삭제)
curl -X DELETE <http://localhost:8080/api/memos/1>
- ID가 1인 메모를 삭제하는 요청
ResponseEntity
- Spring MVC에서 HTTP 요청에 대한 응답을 구성할 때 사용
- 이 클래스는 HTTP 상태 코드, 헤더, 응답 본문을 포함하여 HTTP 응답을 전체적으로 제어 가능
- 이를 통해 개발자는 RESTful API를 보다 세밀하게 조정 가능
- API 사용자는 HTTP 상태 코드를 통해 요청이 성공했는 지, 실패했는지 쉽게 판단 가능
🚀 ResponseEntity와 Builder Pattern의 관계 및 함께 공부해야 하는 이유
return ResponseEntity.status(200).body("성공");
- ResponseEntity.status(200).body("성공")
➡️Builder Pattern 형태를 따름
Builder Pattern
- 객체를 단계별로 구성할 수 있도록 도와주는 생성 패턴
- 메소드 체이닝 (Method Chaining) 사용
ex. 객체.builder().필드1().필드2().build(); - new 키워드를 직접 사용하지 않고 build()를 호출하여 가독성이 좋아진다.
User user = new User.Builder()
.name("Isak")
.age(29)
.build();
정리하자면
ResponseEntity는 Spring에서 제공하는 Builder Pattern 기반의 객체 생성 방식을 활용함
return ResponseEntity
.status(HttpStatus.CREATED) // HTTP 상태 코드 설정
.header("Custom-Header", "HeaderValue") // 응답 헤더 설정
.body(new Memo(1L, "새로운 메모")); // 응답 본문 설정
- Builder Pattern의 메소드 체이닝 사용으로 코드 가독성 향상
- ResponseEntity는 status(), header(), body() 메소드를 체이닝하는 방식 사용
ex. ResponseEntity.ok().body(객체) 같은 코드를 더 직관적으로 이해 가능
🚀회고 결과 :
이번 회고에서는 RESTful API를 복습하고 CURL을 활용해
ResponseEntity 객체를 활용하는 방법에 대해 공부할 수 있었다.
빌더 패턴을 적용하는 방법에 대해서도 추가 공부를 할 수 있었다.
- RESTful API 설계 원칙
- JSON 데이터를 객체(@RequestBody)로 변환
- ResponseEntity를 활용한 명확한 응답 반환 방법
- curl 명령어를 사용하여 API 테스트
느낀 점 :
휴가로 인해 혼자 공부해보니 어려운 점이 많았다. 필요 기능을 찾아서 공부하는 습관을 회고를 통해서 많이 배우고 있다.
이번에 ResponseEntity와 Rest API에서의 메소드 요청 방식 등을 활용하여 다양한 실습을 진행하여
개념을 익힐 수 있었다.
향후 계획 :
- 다른 주제의 프로젝트 실습
- CURL 명령어 연습
- POST 방식을 제공하는 확장 프로그램 사용
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_55일차_"DTO, Security" (0) | 2025.02.26 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_54일차_"@RestController" (0) | 2025.02.25 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_52일차_"RESTful API" (0) | 2025.02.21 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_51일차_"Criteria + hr DB" (0) | 2025.02.20 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_50일차_"Spring Data JPA" (0) | 2025.02.19 |