Skip to content

REST API 개발 가이드

dictionary-api에서 실제로 적용 중인 API 경로 네이밍/컨트롤러 메소드 규칙이다. 새 컨트롤러(메소드)를 추가할 때 이 문서를 템플릿으로 삼는다.

  1. 모든 경로는 /v1/<feature>로 시작하고, 이 부분을 @RequestMapping에 고정한다. /v1은 API 버전이고, <feature>는 소문자 단수 명사 하나다(board, dictionary, menu, search, tools, auth).
  2. <feature>는 패키지명(api/<feature>/...)과 동일하게 맞춘다.
  3. 경로 세그먼트는 kebab-case로 짓는다(find-all, find-by-page, jasypt-encrypt). 대문자, camelCase, snake_case(_)는 쓰지 않는다.

리소스 CRUD 엔드포인트 (board가 원형 템플릿)

Section titled “리소스 CRUD 엔드포인트 (board가 원형 템플릿)”
HTTP Method Path 컨트롤러 메소드명 요청 DTO 응답 비고
POST /find-all findAll RequestDto<XxxFindAllRequestDto> ListResponseDto<XxxResponseDto> 페이지네이션 없는 전체 조회
POST /find-by-page findByPage PaginationRequestDto<XxxFindByPageRequestDto> PaginationResponseDto<XxxResponseDto> 목록 조회는 전부 이 방식
GET /{id} find 없음(@PathVariable long id) ResponseDto<XxxResponseDto> 존재하지 않으면 404 + NOT_FOUND
POST (경로 없음) create RequestDto<XxxCreateRequestDto> ResponseDto<XxxResponseDto>, 201 created(body, URI)로 Location 헤더 포함
PUT /{id} update RequestDto<XxxUpdateRequestDto> ResponseDto<XxxResponseDto>, 200 id 필드를 두지 않고 항상 경로에서만 받는다
DELETE /{id} delete 없음 본문 없음, 204 noContent() 사용
DELETE /delete-all deleteAll RequestDto<XxxDeleteAllRequestDto> 본문 없음, 204 여러 건 일괄 삭제, 아래 예외 규칙 참고
  1. 응답은 공유하고 요청은 나눈다. 응답 DTO(XxxResponseDto)는 find/findAll/findByPage/create/update 다섯 API가 함께 쓰는 클래스 하나다. 요청 DTO(XxxFindAllRequestDto, XxxCreateRequestDto 등)는 API마다 따로 파일과 클래스를 둔다.
  2. find-all/find-by-page는 조회지만 POST를 쓴다. 필터·정렬·페이지 정보를 JSON 본문으로 받는데, GET에 본문을 싣는 것은 HTTP 관례상 피하기 때문이다. 키 하나로 찾는 조회(/{id})만 GET을 쓴다.
  3. 일괄 삭제(deleteAll) 예외 규칙: 단건 delete는 id를 경로로 받아 본문이 필요 없다. 하지만 여러 건을 지우려면 id 목록을 실을 곳이 필요하다. find-all은 이 문제를 POST로 바꿔 풀었지만, deleteAll은 DELETE를 유지한 채 요청 바디를 싣는다. HTTP 스펙상 DELETE에도 바디를 실을 수 있고, Spring(@RequestBody)·RestAssured·axios(axios.delete(url, {data})) 모두 지원한다.
    • 빈 목록은 400: ids가 비어 있으면 @NotEmpty 검증에 걸려 INVALID_PARAMETER(400)로 컨트롤러 진입 전에 거부된다.
    • 부분 삭제는 허용하지 않는다(all-or-nothing). 삭제 전에 repository.findAllById(ids)로 조회한 건수를 요청 개수(중복 제거 후)와 비교한다. 하나라도 없으면 아무것도 지우지 않고 NOT_FOUND(404)로 응답한다.
  4. 엔티티를 응답으로 직접 노출하지 않고, 항상 XxxResponseDto.from(entity)로 변환한다. 감사 컬럼 6개 중 시각(createDt/updateDt)만 응답에 남기고, 사용자 ID·IP는 뺀다.

계층형/참조 데이터 조회 엔드포인트 (menu)

Section titled “계층형/참조 데이터 조회 엔드포인트 (menu)”

리소스 하나를 id로 특정하는 게 아니라 트리 구조를 순회하는 조회는 CRUD 패턴 대신 동사형 경로를 쓴다.

HTTP Method Path 컨트롤러 메소드명 응답
GET /find-root findRoot ListResponseDto<MenuResponseDto>
GET /find-child/{parentId} findChild ListResponseDto<MenuResponseDto>

요청 DTO 없이 @PathVariable만 쓴다(필터 조건이 없으므로 GET으로 충분). 쓰기 API는 없다(참조 데이터 조회 전용).

Section titled “순수 외부 조회 엔드포인트 (search)”

리소스가 아니라 외부 사전별로 별도 조회를 표현하므로 menu와 동일한 패턴(동사형 경로 + @PathVariable)을 쓴다.

HTTP Method Path 컨트롤러 메소드명 응답
GET /find-stdict/{word} findStdict ResponseDto<SearchStdictResponseDto>
GET /find-krdict/{word} findKrdict ResponseDto<SearchKrdictResponseDto>

DB를 거치지 않아 repository/entity가 없다. 두 외부 API의 응답 스키마가 서로 달라(krdict만 영어 번역 포함) 하나로 합쳐 반환하지 않고 API를 나눈다.

커맨드형 유틸리티 엔드포인트 (tools)

Section titled “커맨드형 유틸리티 엔드포인트 (tools)”

암호화/인코딩처럼 리소스 개념이 없는 순수 기능 호출은 POST /v1/tools/<동사-kebab-case> 형태다: jasypt-encrypt, encode-password, compare-password. 경로의 kebab-case 동사구는 컨트롤러 메소드의 camelCase 이름과 1:1 대응한다(jasypt-encrypt → jasyptEncrypt). 단, 결과가 URL만으로 결정되는 순수 조회이자 브라우저가 파일로 저장해야 하는 export-data(테이블 데이터 SQL 내보내기)는 file의 다운로드 엔드포인트와 같은 이유로 예외적으로 GET /v1/tools/export-data/{tableKey}를 쓴다.

표준 동사에 없는(비-CRUD) 액션 메소드 규칙

Section titled “표준 동사에 없는(비-CRUD) 액션 메소드 규칙”

find/findAll/create/update/delete 같은 표준 동사로 표현할 수 없는(리소스가 아니라 “동작”을 표현하는) API에 적용한다(auth의 login/logout, tools의 encodePassword 등).

  1. 우선순위: 조회/생성/수정/삭제로 표현 가능한 API는 이 규칙이 아니라 위 CRUD/조회 규칙을 먼저 따른다.
  2. 형태: 영어 동사(+목적어)를 camelCase로 짓고, 동사를 항상 앞에 둔다(encodePassword, login). 경로는 같은 문구를 kebab-case로 옮긴다(encode-password).
  3. HTTP Method: 상태를 변경하는(멱등하지 않은) 액션은 POST로 고정한다. 결과가 URL만으로 결정되는 순수 조회 액션이면 예외적으로 GET도 허용한다(파일 다운로드 등).
  4. 동사 선택: find/create/update/delete처럼 CRUD로 오해될 수 있는 동사는 피하고, 의미가 분명한 동사(encode, compare, login)를 쓴다.

공통 메소드/경로 네이밍 규칙 정리

Section titled “공통 메소드/경로 네이밍 규칙 정리”
  1. 경로(kebab-case)와 메소드명(camelCase)은 1:1로 대응한다: find-by-page → findByPage, find-root → findRoot. 경로를 그대로 camelCase로 옮기고, 줄이거나 다르게 짓지 않는다.
  2. 조회 메소드는 find로 시작한다: find(단건), findAll(전체), findByPage(페이지), findRoot/findChild(계층). get/retrieve/list 같은 다른 동사는 쓰지 않는다.
  3. 쓰기 메소드는 create/update/delete만 쓴다. save/modify/remove 같은 동의어는 쓰지 않고, 일괄 삭제는 deleteAll로 짓는다.
  4. 컨트롤러 클래스의 상속·애너테이션 구성은 패키지 가이드 참고. 필드는 생성자로 주입받는 서비스 하나만 둔다.
  5. 응답은 항상 ok()/created()/noContent() 헬퍼로 감싼다. ResponseEntity.status(...)를 직접 호출하지 않는다.
  6. 요청 검증은 @Valid @RequestBody로 컨트롤러 진입점에서 수행한다(서비스 레이어에서 재검증하지 않는다).
  7. Swagger 문서화: 클래스에는 기능 이름을 한글로 적은 @Tag(name, description)을, 메소드에는 @Operation(summary, description)을 붙인다. 200이 아닌 응답(201/204)은 @ApiResponse로 명시한다.
  8. 로그인 없이 열어야 하는 API는 클래스 또는 메소드에 @PublicApi를 붙인다(붙이지 않으면 기본값은 로그인 필요).

DTO 클래스 네이밍(<Feature> 접두어)

Section titled “DTO 클래스 네이밍(<Feature> 접두어)”

모든 DTO 클래스는 클래스명 맨 앞에 기능 이름을 접두어로 붙인다(BoardCreate, ToolsJasyptEncrypt). 자세한 이유는 패키지 가이드의 “DTO 클래스 네이밍” 항목 참고.