URI는 자원을 식별하는 문자열이고, URL은 그중 자원의 위치와 접근 방법을 나타내는 주소다. Endpoint는 클라이언트가 요청을 보내는 API 접점으로 이해할 수 있다.

RFC 3986에서는 URI를 리소스를 식별하기 위한 문자열로 설명한다. 여기서 리소스는 실제 파일일 수도 있고, 사용자 정보나 주문 정보처럼 추상적인 대상일 수도 있다.
MDN의 URL 문서에서는 URL을 인터넷에서 특정 리소스가 어디에 있는지 알려주는 주소로 설명한다. 예를 들어 다음은 URL이다.

https://developer.mozilla.org/en-US/docs
Spring 공식 문서의 Request Mapping 설명에서는 @RequestMapping, @GetMapping, @PostMapping 같은 어노테이션으로 HTTP 요청을 Controller 메서드에 매핑한다고 설명한다.
즉, Spring Boot에서 우리가 흔히 말하는 Endpoint는 보통 이런 조합으로 이해할 수 있다.
HTTP Method + Path → Controller Handler Method
예를 들면 다음은 하나의 API Endpoint라고 볼 수 있다.
GET /users/{id}
처음에는 이 세 단어가 거의 같은 말처럼 느껴진다.
URI
URL
Endpoint
실제로 실무에서도 아주 엄격하게 구분하지 않고 섞어 말하는 경우가 있다.
예를 들어 누군가 이렇게 말할 수 있다.
"회원 조회 URL 알려주세요."
"회원 조회 URI가 뭐예요?"
"회원 조회 Endpoint가 어디예요?"
대화에서는 셋 다 비슷하게 들릴 수 있다. 하지만 정확히 보면 관심사가 조금씩 다르다.
URI: 어떤 자원을 식별하는가?
URL: 그 자원에 어디로, 어떤 방식으로 접근하는가?
Endpoint: 어떤 요청 조건이 어떤 서버 기능으로 연결되는가?
도서관 책으로 비유해보자.
책의 고유 번호가 있다.
ISBN: 978-89-123-4567-0
이 번호는 책을 식별한다. 어디에 있는지는 몰라도 어떤 책인지는 알 수 있다. URI는 이런 식별자에 가깝다.
그 책이 실제로 어디 꽂혀 있는지도 알 수 있다.
서울 도서관 3층 개발서적 A-12 칸
이건 위치 정보다. URL은 자원의 위치를 알려주는 주소에 가깝다.
이제 도서관 직원에게 책 정보를 요청하는 창구가 있다고 해보자.
책 정보 조회 창구
사용자는 이 창구를 통해 책 정보를 요청한다. 백엔드에서 Endpoint는 이런 창구에 가깝다.
API로 바꾸면 이렇게 볼 수 있다.
URI: /users/1
URL: https://api.example.com/users/1
Endpoint: GET /users/{id}
각각의 느낌은 다르다.
/users/1
-> 사용자 1번이라는 자원을 식별한다.
https://api.example.com/users/1
-> 사용자 1번 자원이 있는 전체 주소다.
GET /users/{id}
-> 사용자 상세 조회 요청을 처리하는 API 접점이다.
Spring Boot에서 사용자 조회 API를 만든다고 해보자.
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
return new UserResponse(id, "junior-backend");
}
}

이 코드에서 각각을 나눠보면 다음과 같다.
URI Path:
/users/{id}
URL 예시:
https://api.example.com/users/1
Endpoint:
GET /users/{id}
Controller Method:
getUser(Long id)
실제 클라이언트는 이렇게 요청할 수 있다.
GET https://api.example.com/users/1 HTTP/1.1
Accept: application/json
서버는 이 요청을 보고 다음처럼 판단한다.
1. HTTP Method가 GET이다.
2. Path가 /users/1이다.
3. /users/{id} 패턴과 일치한다.
4. id 값은 1이다.
5. UserController의 getUser 메서드를 실행한다.
응답은 이렇게 돌아올 수 있다.
{
"id": 1,
"name": "junior-backend"
}
여기서 중요한 점은 Endpoint가 단순히 주소만 의미하지 않는다는 것이다.

GET /users/1
PUT /users/1
DELETE /users/1
이 세 요청은 path는 비슷하지만 의미가 다르다.
GET /users/1
→ 사용자 조회
PUT /users/1
→ 사용자 정보 교체 또는 수정
DELETE /users/1
→ 사용자 삭제
그래서 API를 말할 때는 가능하면 HTTP Method까지 같이 말하는 것이 좋다.

첫째, URL과 Endpoint를 말할 때 HTTP Method를 빼먹지 않는 것이 좋다.
애매한 표현:
/users API
더 명확한 표현:
GET /users
POST /users
GET /users/{id}
DELETE /users/{id}
/users라는 경로 하나에도 여러 API가 있을 수 있다.
@GetMapping("/users")
public List<UserResponse> getUsers() {
// 사용자 목록 조회
}
@PostMapping("/users")
public UserResponse createUser(@RequestBody UserCreateRequest request) {
// 사용자 생성
}
둘 다 /users를 사용하지만 Endpoint는 다르다.
GET /users
POST /users
둘째, URL에 동사를 너무 많이 넣지 않는 것이 좋다.
아쉬운 예:
GET /getUserById?id=1
POST /createUser
POST /deleteUser
REST API에서는 보통 리소스를 중심으로 path를 만들고, 동작은 HTTP Method로 표현한다.
좋은 방향:
GET /users/1
POST /users
DELETE /users/1
셋째, URI를 화면 기준으로만 만들면 나중에 API가 흔들릴 수 있다.
예를 들어 관리자 화면에서 사용자 목록을 본다고 해서 이렇게 만들 수 있다.
GET /admin-user-list-page-data
처음에는 편해 보이지만, 나중에 모바일 앱이나 다른 화면에서도 사용자 목록이 필요해지면 애매해진다.
조금 더 리소스 중심으로 보면 다음이 낫다.
GET /admin/users
또는 권한 처리를 분리할 수 있다면 다음처럼 더 일반화할 수도 있다.
GET /users
API path는 화면 이름보다 서버가 다루는 자원 중심으로 잡는 편이 오래 간다.
URI, URL, Endpoint는 비슷해 보이지만 관점이 다르다.
URI:
자원을 식별하는 문자열
URL:
자원의 위치를 알려주는 주소
Endpoint:
클라이언트가 호출하는 API 접점
백엔드 API를 만들 때는 보통 이렇게 말하면 명확하다.
GET /users/{id}
POST /users
DELETE /users/{id}
나는 이 세 단어를 이렇게 이해하고 있다.
URI는 “무엇인지”, URL은 “어디 있는지”, Endpoint는 “어떻게 요청할지”에 가깝다.
처음에는 용어 차이가 사소해 보일 수 있다. 하지만 API 문서를 쓰거나 프론트엔드와 협업할 때는 이 차이가 꽤 중요하다.
특히 “이 API 어디로 호출해요?”라는 질문에 답할 때는 주소만 말하기보다 HTTP Method까지 함께 말하는 습관을 들이는 것이 좋다.
GET /users/{id}로 호출하면 됩니다.
이렇게 말하면 요청의 대상과 의도가 함께 전달된다.