GET/POST/PUT/DELETE 사용 패턴
같은 HTTP Method라도 dictionary-api의 기능마다 실제로 쓰는 패턴이 다르다. 기능별 실제 컨트롤러 코드로
“이럴 땐 이 Method, 이런 모양”을 정리한다. 경로/메소드명 네이밍 규칙 자체는 REST API 개발
가이드 참고.
- 단건 조회(
/{id}) —board가장 기본적인 형태 — 요청 DTO 없이@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ResponseDto<BoardResponseDto>> find(@PathVariable final long id) { ... }@PathVariable하나만 받는다. 존재하지 않으면 서비스가BizException(NOT_FOUND)를 던져 404로 응답한다. 전체 코드는 MVC 코드 작성 가이드의 Controller 항목 참고. - 계층형 조회(동사 경로 +
@PathVariable) —menu리소스 하나를api/menu/controller/MenuController.java @GetMapping(value = "/find-root", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ListResponseDto<MenuResponseDto>> findRoot() {final List<MenuResponseDto> payload =menuService.findRoot().stream().map(MenuResponseDto::from).toList();return ok(new ListResponseDto<>(payload));}@GetMapping(value = "/find-child/{parentId}", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ListResponseDto<MenuResponseDto>> findChild(@PathVariable final long parentId) {final List<MenuResponseDto> payload =menuService.findChild(parentId).stream().map(MenuResponseDto::from).toList();return ok(new ListResponseDto<>(payload));}id로 특정하는 게 아니라 트리를 순회하는 조회 — 필터 조건이 없어 요청 DTO 없이GET으로 충분하다. - 순수 외부 API 조회 —
searchDB를 거치지 않아api/search/controller/SearchController.java @GetMapping(value = "/find-stdict/{word}", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ResponseDto<SearchStdictResponseDto>> findStdict(@PathVariable final String word) {return ok(new ResponseDto<>(searchService.findStdict(word)));}repository/entity가 없다 — 서비스가 외부 API 클라이언트(StdictClient)를 바로 호출해 변환한 응답을 돌려준다. - 파일 다운로드(JSON이 아닌 바이너리 응답) —
file상태를 바꾸지 않는 순수 조회이고, 프런트엔드가api/file/controller/FileController.java @GetMapping("/download/{fileKey}")public ResponseEntity<byte[]> download(@PathVariable final String fileKey) {final FileDownloadContext context = fileService.download(FileKey.from(fileKey));return ok(context.getContent(), context.getFileName(), context.getContentType());}<a href>로 직접 이동시켜야 브라우저가 파일 저장 UI를 띄우므로GET을 쓴다. 응답을ResponseDto(JSON)로 감싸지 않고ResponseEntity<byte[]>에Content-Disposition: attachment를 직접 실어 반환한다.
- 필터 조건이 있는 목록 조회 —
board조회인데도api/board/controller/BoardController.java @PostMapping(value = "/find-by-page",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<PaginationResponseDto<BoardResponseDto>> findByPage(@Valid @RequestBody final PaginationRequestDto<BoardFindByPageRequestDto> request) {return ok(new PaginationResponseDto<>(boardService.findByPage(request).map(BoardResponseDto::from)));}GET이 아니라POST인 이유: 필터·정렬·페이지 정보를 JSON 본문으로 받아야 하기 때문이다(GET에 본문을 싣는 것은 HTTP 관례상 지양한다). - 리소스 생성(201 +
Location헤더) —board@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ResponseDto<BoardResponseDto>> create(@Valid @RequestBody final RequestDto<BoardCreateRequestDto> request) { ... }created(body, URI)로 생성된 리소스의 위치를Location헤더에 실어 응답한다. 전체 코드는 MVC 코드 작성 가이드의 Controller 항목 참고. - 상태를 바꾸는 인증 액션(리소스가 아니라 “동작”) —
auth로그인/로그아웃처럼 리소스가 아니라 “동작”을 표현하는 API는 명사(api/auth/controller/AuthController.java @PublicApi@PostMapping("/login")public ResponseEntity<ResponseDto<SessionUser>> login(@Valid @RequestBody RequestDto<AuthLoginRequestDto> request) {final SessionUser sessionUser = authService.login(request);return ok(new ResponseDto<>(sessionUser));}/{id})가 아니라 동사 경로를 쓰고, 세션 생성처럼 멱등하지 않은 상태 변경이라POST를 쓴다. - 커맨드형 유틸리티(리소스 개념이 없는 순수 기능 호출) —
toolsapi/tools/controller/ToolsController.java @PostMapping("/jasypt-encrypt")public ResponseEntity<ResponseDto<ToolsJasyptEncryptResponseDto>> jasyptEncrypt(@Valid @RequestBody RequestDto<ToolsJasyptEncryptRequestDto> request) {final ToolsJasyptEncryptContext context = request.getPayload().toContext();final ToolsJasyptEncryptResponseDto payload =ToolsJasyptEncryptResponseDto.from(toolsService.jasyptEncrypt(context));return ok(new ResponseDto<>(payload));}Entity가 없는 기능이라RequestDto/ResponseDto사이 중간 표현으로Context클래스를 쓴다 (toContext()로 변환).
- 리소스 수정 —
boardapi/board/controller/BoardController.java @GuestForbidden@PutMapping(value = "/{id}",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<ResponseDto<BoardResponseDto>> update(@PathVariable final long id,@Valid @RequestBody final RequestDto<BoardUpdateRequestDto> request) {final BoardEntity result = boardService.update(id, request);return ok(new ResponseDto<>(BoardResponseDto.from(result)));}id는 요청 DTO 필드가 아니라 항상 경로에서만 받는다 — 수정 대상은 URL이 정하고, 본문은 바뀔 값만 담는다.
DELETE
Section titled “DELETE”- 단건 삭제(본문 없음) —
board@DeleteMapping(value = "/{id}")public ResponseEntity<Void> delete(@PathVariable final long id) { ... }id를 경로 변수로만 받아 본문이 필요 없다 — 존재하지 않으면 404, 성공하면 204(No Content). 전체 코드는 MVC 코드 작성 가이드의 Controller 항목 참고. - 일괄 삭제(예외적으로 요청 바디를 싣는
DELETE) —boardapi/board/controller/BoardController.java @GuestForbidden@DeleteMapping(value = "/delete-all", consumes = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<Void> deleteAll(@Valid @RequestBody final RequestDto<BoardDeleteAllRequestDto> request) {boardService.deleteAll(request);return noContent();}find-all과 달리 HTTP Method를 바꾸지 않고DELETE를 그대로 유지하면서 삭제할id목록을 요청 바디에 싣는다 — 하나라도 존재하지 않는id가 섞여 있으면 아무것도 지우지 않고 404(NOT_FOUND)로 응답한다(all-or-nothing). 이 방식을 택한 이유는 REST API 개발 가이드의 일괄 삭제 예외 규칙 참고.