백엔드 개발에서 API 명세 설계는
실제 서버 기능을 클라이언트가 어떻게 호출할지 “계약서” 같은 역할을 해요.
실무에서는 엔드포인트, HTTP 메서드, 요청/응답 형식, 상태 코드 등을 문서로 정리합니다.
예시를 보여드릴게요.
1️⃣ 회원 관련 API 명세 예시 (실무 스타일)
| 기능 | URL | HTTP 메서드 |
요청 Body | 응답 Body | 상태 코드 |
| 회원 가입 | /api/users | POST | { "name": "Alice", "email": "alice@example.com", "password": "1234" } |
{ "id": 1, "name": "Alice", "email": "alice@example.com" } |
201 Created |
| 로그인 | /api/users/login | POST | { "email": "alice@example.com", "password": "1234" } |
{ "token": "jwt-token-string" } | 200 OK / 401 Unauthorized |
| 회원 정보 조회 | /api/users/{id} | GET | – | { "id": 1, "name": "Alice", "email": "alice@example.com" } |
200 OK / 404 Not Found |
| 회원 정보 수정 | /api/users/{id} | PUT | { "name": "Alice2" } | { "id": 1, "name": "Alice2", "email": "alice@example.com" } |
200 OK / 400 Bad Request |
| 회원 탈퇴 | /api/users/{id} | DELETE | – | { "message": "User deleted" } |
200 OK / 404 Not Found |
Q. 201 Created ?
1️⃣ 기본 의미
- 201 Created = “요청이 성공적으로 처리되어 서버에 새로운 리소스가 생성되었다”
- 주로 POST 요청 후 서버에서 새로운 데이터(회원, 글, 티켓 등)를 만들었을 때 사용
2️⃣ 예시
회원 가입
POST /api/users
Content-Type: application/json
{
"name": "Alice",
"email": "alice@example.com",
"password": "1234"
}
서버 응답:
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
- 상태 코드 201 → 새로운 회원(id=1) 생성 완료
- 응답 body에 생성된 리소스 정보 포함
티켓 구매
POST /api/events/1/tickets
{
"userId": 1,
"quantity": 2
}
서버 응답:
HTTP/1.1 201 Created
{
"ticketId": 101,
"eventId": 1,
"quantity": 2
}
- 상태 코드 201 → 새 티켓 생성 완료
3️⃣ 정리
| 상태 코드 | 의미 | 사용 예시 |
| 200 OK | 요청 성공, 기존 리소스 조회/수정 | GET, PUT |
| 201 Created | 요청 성공, 새로운 리소스 생성 | POST → 회원가입, 글쓰기, 티켓 구매 |
| 400 Bad Request | 클라이언트 요청 오류 | 필수 데이터 누락 |
| 401 Unauthorized | 인증 실패 | 로그인 필요 |
| 404 Not Found | 요청한 리소스 없음 | 잘못된 URL |
💡 핵심:
- 201은 POST 요청 후 “새로운 게 만들어졌다” 라고 알려주는 코드
- 단순 조회(GET)나 수정(PUT)에는 사용하지 않음
Q. 클라이언트 오류 상태 코드 4xx
HTTP 상태 코드에서 4xx는 클라이언트 오류(Client Error) 범주입니다.
1️⃣ 4xx 상태 코드의 의미
- 클라이언트 요청이 잘못됨 → 서버가 요청을 이해했지만 처리할 수 없음
- 주로 사용자 입력, 인증, 권한, URL 오류 등으로 발생
2️⃣ 주요 4xx 코드
| 코드 | 이름 | 의미 |
| 400 | Bad Request | 요청 문법이 잘못되었거나 서버가 이해할 수 없음 |
| 401 | Unauthorized | 인증 필요/실패 → 로그인 필요 |
| 403 | Forbidden | 인증은 됐지만 권한 없음 → 접근 금지 |
| 404 | Not Found | 요청한 리소스가 없음 → URL 오류나 존재하지 않는 페이지 |
| 405 | Method Not Allowed | 요청 메서드(GET, POST 등)가 허용되지 않음 |
| 408 | Request Timeout | 클라이언트 요청 시간이 너무 오래 걸려서 서버가 종료 |
| 429 | Too Many Requests | 요청이 너무 많음 → Rate Limiting |
3️⃣ 특징
- 클라이언트 원인: 서버 문제 아님
- 재시도 여부: 상황에 따라 다름
- 인증/권한 문제 → 올바른 인증 후 재시도 가능
- 잘못된 요청/URL → 수정 후 재시도
- 에러 메시지(body): JSON 형식으로 상세 정보 포함 가능
💡 요약
4xx = 클라이언트 요청 오류
- 401 = 인증 필요
- 403 = 권한 부족
- 404 = 리소스 없음
- 400 = 문법/형식 오류
Q. 사용자 요청 4xx 401 Unauthorized vs 403 Forbidden
1️⃣ 의미
- 401 Unauthorized = 요청한 리소스에 접근하려고 했지만 인증(authentication)이 필요하거나 인증이 실패한 상태
- 즉, 로그인하지 않았거나 잘못된 인증 정보를 보내서 서버가 접근을 거부한 경우
2️⃣ 특징
| 특징 | 설명 |
| 인증 필요 | 클라이언트가 로그인, 토큰, API 키 등 인증 정보 필요 |
| 재시도 가능 | 올바른 인증 정보를 보내면 접근 가능 |
| 클라이언트 책임 | 서버 측에서 "인증 실패"를 알려주는 상태 코드 |
3️⃣ 예시 상황
(1) 토큰 없는 요청
GET /api/users HTTP/1.1
Host: example.com
- 서버 응답:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
(2) 잘못된 토큰
Authorization: Bearer invalid-token
- 서버 응답: 401 Unauthorized
4️⃣ 401 vs 403 차이
| 상태 코드 | 의미 |
| 401 Unauthorized | 인증(authentication) 필요 / 실패 → 로그인 필요 |
| 403 Forbidden | 인증은 됐지만, 접근 권한(permission) 없음 → 접근 금지 |
💡 요약:
401 = “누구인지 확인 안 됨(로그인 필요)”
403 = “누구인지 확인 됐지만, 접근 권한 없음”
Q. 400 Bad Request 발생 사례
400 Bad Request는 말 그대로 클라이언트 요청 문법이나 형식이 잘못되었을 때 발생합니다.
구체적인 예시를 들어 설명해볼게요.
1️⃣ 400 Bad Request 발생 사례
(1) 잘못된 JSON
// body에 올바르지 않은 JSON 전달
body: '{"name": "Alice", "age": 23,' // 마지막 쉼표 때문에 문법 오류
- 서버가 JSON 파싱을 시도하다가 실패 → 400 반환
(2) 잘못된 쿼리스트링
GET /api/users?id==123 HTTP/1.1
- id==123처럼 문법 오류 있는 쿼리
- 서버가 파라미터 해석 실패 → 400 반환
(3) 필수 파라미터 누락
// 서버에서 name 필수인데 body에 없음
body: '{"password":"1234"}'
- 요청은 문법상 JSON이지만, 서버에서 필수 값이 없다고 판단 → 400 반환
(4) 잘못된 Content-Type
POST /api/users
Content-Type: text/plain
body: '{"name":"Alice","password":"1234"}'
- 서버가 application/json을 기대했는데 잘못된 Content-Type → 400 반환
(5) 너무 큰 요청, 형식 불일치 등
- 예: URL, 헤더, 쿠키 길이 초과
- 잘못된 form-data 형식
2️⃣ 요약
- 400 Bad Request = 클라이언트가 서버가 이해할 수 없는 요청을 보냈을 때
- 발생 원인:
- 잘못된 문법(JSON, 쿼리스트링 등)
- 필수 파라미터 누락
- Content-Type/헤더 불일치
- URL, body, 쿠키 등 형식 오류
Q. 쿼리스트링 문법
쿼리스트링(Query String) 문법은 JSON과는 완전히 다릅니다. 혼동하면 안 돼요.
1️⃣ 쿼리스트링 기본 구조
?key1=value1&key2=value2&key3=value3
- ? 뒤에 키=값 쌍을 &로 연결
- 예:
/api/users?id=alice&password=1234
2️⃣ 값(Value) 표기 규칙
- 문자열 → 따옴표 안 씀, URL 인코딩 필요할 때만 변환
- 예: 공백 → %20 / 특수문자 → URL 인코딩
- ❌ /api/users?id="alice" → 따옴표 그대로 전송됨 → 값에 포함
- ✅ /api/users?id=alice
- 숫자 → 그대로 작성
- 예: /api/users?age=23
- 부울(boolean) → 소문자 true/false 그대로
- 예: /api/users?isAdmin=true
3️⃣ 예시 비교
| 형태 | 올바른 쿼리스트링 | 설명 |
| 문자열 | id=alice | 따옴표 불필요 |
| 숫자 | age=23 | 그대로 사용 |
| 공백/특수문자 | name=Alice%20Smith | URL 인코딩 필요 |
| 잘못된 예 | id="alice" | 따옴표가 값에 포함됨 → 서버에서 "alice" 그대로 읽음 |
4️⃣ 주의점
- 쿼리스트링은 JSON 문법이 아님
- 따옴표, 중괄호, 콤마 등 사용 X
- 모든 값은 문자열로 전송되므로, 서버에서 타입 변환 필요 (parseInt, Boolean() 등)
💡 요약
쿼리스트링: ?key=value&key2=value2
- 문자열, 숫자, boolean 모두 따옴표 없이
- 공백/특수문자는 URL 인코딩
- JSON 문법과 혼동하지 않기
Q. 쿼리스트링으로 문자열을 보내면, 서버에서 자동으로 타입 변환을 하는가?
아니요! 😄 쿼리스트링으로 보내면 서버에서 자동으로 타입 변환은 안 됩니다.
1️⃣ 쿼리스트링 전송 특징
- 모든 값은 문자열(String) 형태로 전송됨
- 예시:
/api/users?age=23&isAdmin=true
- 서버에서 실제로 받는 값:
req.query.age // "23" (문자열)
req.query.isAdmin // "true" (문자열)
2️⃣ 타입 변환 필요
- 서버에서 숫자, boolean 등 원래 타입으로 사용하려면 명시적 변환 필요
const age = parseInt(req.query.age); // "23" → 23
const isAdmin = req.query.isAdmin === "true"; // "true" → true
- 서버 프레임워크에 따라 helper 기능이 제공될 수 있지만,
기본적으로는 문자열 그대로 받음
3️⃣ 요약
| 데이터 타입 | 쿼리스트링 전송 | 서버 수신 | 필요 조치 |
| 숫자 | age=23 | "23" | parseInt() |
| boolean | isAdmin=true | "true" | 비교 또는 Boolean 변환 |
| 문자열 | name=Alice | "Alice" | 그대로 사용 |
💡 결론:
쿼리스트링으로 보내면 모든 값은 문자열
서버에서 원래 타입으로 변환하려면 개발자가 명시적으로 처리해야 함
Q. API 명세에서 URL
1️⃣ API 명세에서 URL
- 보통 URL에는 host(도메인) 부분을 생략하고 path만 작성합니다.
- 이유: 서버 주소(host)는 보통 공용 환경 변수나 config로 관리되므로 반복적으로 적지 않아요.
- 예시:
POST /api/users ← path만 명세
GET /api/events/{id}
- 실제 요청 시에는 https://example.com 같은 host를 붙여서 완전한 URL이 됩니다:
https://example.com/api/users
https://example.com/api/events/{id}
2️⃣ 요청 보낼 서버 IP 주소 / 도메인
- Host 헤더에 나옵니다.
- 예시 요청:
GET /api/users HTTP/1.1
Host: example.com
- Host = 서버 도메인(example.com)
- 브라우저가 내부적으로 DNS 조회 후 실제 IP(203.0.113.5)로 연결하지만, 요청 헤더에는 도메인 이름이 들어갑니다.
3️⃣ Referrer 헤더
- Referrer 헤더 = "이 요청을 보낸 페이지 주소"
- 즉, 사용자가 클릭하거나 JS fetch/axios로 요청을 보낸 현재 페이지 URL
예:
Referrer: https://myapp.com/page1
4️⃣ Origin 헤더
- Origin 헤더 = "요청을 보낸 출처(origin)"
- origin = scheme + host + port
- 주로 CORS 요청에서 서버가 출처를 확인하도록 보내는 헤더
예:
Origin: https://myapp.com
- fetch/axios 등 JS에서 POST, PUT, DELETE 요청 시 자동으로 포함됨
- GET 요청은 대부분 브라우저에서 Origin 헤더를 안 붙이기도 함
✅ 정리
| 항목 | 의미 | 예시 |
| API 명세 URL | host 생략, path만 | /api/users |
| Host 헤더 | 요청할 서버 도메인 | Host: example.com |
| Referrer 헤더 | 현재 페이지 URL | Referrer: https://myapp.com/page1 |
| Origin 헤더 | 요청을 보낸 출처(origin) | Origin: https://myapp.com |
Q. host 헤더에는 path, 쿼리스트링은 포함 안 돼? 서브도메인은 포함되고?
1️⃣ Host 헤더에 포함되는 것
- Host 헤더에는 도메인 이름 + 포트 번호만 들어갑니다.
- 포함됨:
- 서브도메인 → 예: api.myapp.com
- 포트 번호 → 기본 포트가 아니면 필요: api.myapp.com:8080
- 포함되지 않음:
- path → /users
- 쿼리 스트링 → ?id=10&name=alice
예시:
GET /api/users?id=10 HTTP/1.1
Host: api.myapp.com:8080
- 요청라인(Request Line)에 path + query가 있고,
- Host 헤더에는 host + port만 들어감
2️⃣ 왜 path와 query는 Host에 안 들어가는가?
- HTTP 스펙상 요청 라인(Request Line)이 따로 있기 때문
- 여기서 이미 서버가 path + query를 알 수 있음
GET /api/users?id=10 HTTP/1.1
- Host 헤더는 서버가 어떤 도메인/포트로 요청 받았는지 식별 용도만
✅ 정리
| 항목 | 포함 여부 |
| 서브도메인 | ✅ 포함 (api.myapp.com) |
| 포트 번호 | ✅ 포함 (:8080) |
| path | ❌ 포함 안 됨 (/users) |
| 쿼리 스트링 | ❌ 포함 안 됨 (?id=10) |
즉, Host 헤더는 **“어떤 서버에 보냈는지”**만 명시하고,
path/쿼리/fragment 같은 리소스 위치 정보는 Request Line에 있음.
Q. 실제 네트워크탭 - headers 탭에는 host 헤더가 없는데?
그리고 origin, referrer 둘다 요청을 보낸 출처잖아. 둘의 차이점을 알려줘
1️⃣ 왜 Network 탭에 Host 헤더가 안 보이는가?
- Host 헤더는 HTTP/1.1 요청에 필수이지만,
- 브라우저 개발자도구에서는 기본값 Host 헤더를 생략하고 안 보여주는 경우가 있습니다.
- 실제 네트워크 패킷에는 분명 포함되어 있어요.
- 확인하려면 curl -v https://example.com 이나 Wireshark 같은 패킷 캡처 도구로 보면 Host 헤더 확인 가능
💡 요약:
브라우저 DevTools Network 탭은 일부 헤더(특히 기본값 헤더)를 생략해서 표시할 수 있음.
하지만 서버에는 항상 Host 헤더가 전송됨.
2️⃣ Referrer vs Origin
둘 다 **“요청을 보낸 출처”**라는 점에서 비슷하지만, 목적과 범위가 달라요.
| 항목 | 의미 | 범위/형식 | 용도 |
| Referrer | 현재 페이지 URL | scheme + host + port + path + query (fragment 제외) | “어떤 페이지에서 왔는지” 추적/통계 |
| Origin | 요청을 보낸 출처(origin) | scheme + host + port | “이 출처가 허용된 출처인지” 보안 확인 (CORS) |
2-1️⃣ 예시
현재 페이지:
https://api.myapp.com/userLogin?id=name&password=123#section1
- 요청 서버: https://api.myapp.com/success → 같은 origin
- Referrer → https://api.myapp.com/userLogin?id=name&password=123
- Origin → https://api.myapp.com
- 요청 서버: https://api.yourapp.com/index.html → 다른 origin
- Referrer → https://api.myapp.com/userLogin?id=name&password=123 (경우에 따라 strict policy로 origin만 보낼 수도 있음)
- Origin → https://api.myapp.com
2-2️⃣ 핵심 차이
- 범위
- Referrer: full URL (path, query 포함)
- Origin: scheme + host + port만
- 주 용도
- Referrer: “어떤 페이지에서 왔는지” 추적/통계
- Origin: “이 출처가 허용된 출처인지” 보안 확인(CORS)
즉, Origin은 Referrer보다 더 간결하고 보안 중심, Referrer는 상세 경로 정보까지 포함이라고 생각하면 이해하기 편해요.
Q. API 명세 URL / 요청라인 Path에 쿼리스트링, 해시 들어가는지?
API 명세와 HTTP 요청라인에서 path 뒤에 쿼리스트링과 fragment를 넣는 것이 허용되는지,
그리고 주의할 점을 정리해드릴게요.
1️⃣ HTTP 요청라인에서
GET /api/users?id=name&password=1234#section1 HTTP/1.1
- 쿼리스트링 (?id=…): ✅ 포함 가능
- 서버가 요청 파라미터로 읽을 수 있음
- fragment (#section1): ❌ 포함 안 됨
- fragment는 브라우저에서만 쓰는 클라이언트 내부 위치(anchor)
- 서버로는 절대 전송되지 않음
즉, HTTP 요청에서는 fragment를 제거한 path + query만 사용합니다.
2️⃣ API 명세에서 URL
- 보통 API 명세는 path + query만 작성합니다.
- 예시:
GET /api/users?id={id}&password={password}
- fragment(#)는 쓰지 않음
- API 문서는 서버 입장에서만 필요한 정보니까, 클라이언트 내부 위치는 의미가 없음
3️⃣ 요약
| 항목 | 포함 가능 여부 | 비고 |
| Path (/api/users) | ✅ | 필수 |
| Query string (?id=…&password=…) | ✅ | 서버 요청 파라미터로 사용 |
| Fragment (#section1) | ❌ | 브라우저 전용, 서버로 전송되지 않음 |
💡 결론
- HTTP 요청라인과 API 명세 URL 둘 다 path + query까지 작성 가능,
- fragment(#)는 항상 생략해야 함
Q. POST 요청 - body vs URL 쿼리스트링
이건 웹 개발에서 POST 요청 Body vs URL 쿼리스트링 차이를 이해하는 핵심 포인트예요. 😄
1️⃣ 쿼리스트링(Query String) 방식
예시:
POST /api/users/?name=Alice&password=1234 HTTP/1.1
- 데이터가 URL에 포함됨 (?name=Alice&password=1234)
- 특징:
- 브라우저/서버 모두 쉽게 읽을 수 있음
- 길이 제한이 있을 수 있음 (브라우저마다 다름, 일반적으로 ~2000~8000자)
- URL에 그대로 노출 → 보안 민감 정보(패스워드 등)에는 부적합
- GET 요청에서도 쓸 수 있음
2️⃣ Request Body 방식 (JSON)
예시:
POST /api/users HTTP/1.1
Content-Type: application/json
{
"name": "Alice",
"password": "1234"
}
- 데이터가 HTTP Body에 포함됨
- 특징:
- 길이 제한이 거의 없음 (서버 설정에 따라 다름)
- 보안상 URL에 노출되지 않음 → 민감 데이터 전송에 적합
- POST, PUT, PATCH 등 데이터 생성/수정 용도로 사용
- 서버가 JSON으로 바로 파싱 가능
3️⃣ 비교 정리
| 항목 | 쿼리스트링 | Request Body (JSON) |
| URL 노출 | ✅ | ❌ |
| 길이 제한 | 있음 (~2000~8000자) | 거의 없음 |
| 보안 | 패스워드 등 민감 데이터 부적합 | 적합 |
| 용도 | 검색, 필터, GET 요청 데이터 | 생성/수정, POST/PUT/PATCH 요청 데이터 |
| 서버 처리 | req.query (Express 기준) | req.body (Content-Type: application/json) |
💡 결론
- 검색, 필터, 조회 같은 데이터 → 쿼리스트링
- 회원가입, 로그인, 데이터 생성/수정 같은 민감/대량 데이터 → POST Body(JSON)
1️⃣ POST 요청 시 데이터 전송 방식
(1) Request Body
- POST/PUT/PATCH 등 데이터 생성/수정 요청에서 표준적 방식
- JSON, XML, form-data 등 다양한 형식 가능
(2) 쿼리스트링으로 보내는 방법도 가능?
- 기술적으로는 가능:
POST /api/users?name=Alice&password=1234 HTTP/1.1
- 하지만 권장하지 않음:
- URL에 데이터가 노출 → 보안 위험
- 길이 제한 있음
- 서버 프레임워크 관습상 POST/PUT은 Body에서 데이터 받도록 구현됨
✅ 정리:
POST 요청에서 데이터는 원칙적으로 Body에 담고, Content-Type 헤더로 타입 지정
2️⃣ JSON 파싱(parse) 의미
- JSON 문자열 ↔ JS 객체 간 변환 모두 “파싱”이라고 표현함.
- 엄밀히 구분하면:
- JSON.parse(string) → JSON 문자열 → JS 객체 (주로 “파싱”이라고 함)
- JSON.stringify(obj) → JS 객체 → JSON 문자열 (보통 “직렬화(serialization)”라고 더 정확히 표현)
- 그런데 개발 문서에서는 둘 다 **“JSON 처리/파싱”**이라고 편하게 쓰는 경우 많아요.
3️⃣ POST 요청에서 JSON 처리 흐름
- 클라이언트(브라우저/JS)
- JS 객체를 그대로 body에 넣을 수는 없음
- 반드시 문자열로 변환해야 함 (HTTP Body는 문자열 전송)
- 예:
fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice", password: "1234" })
})
- 서버
- Body는 문자열(JSON) 형태로 수신됨
- 서버에서 JSON → 객체로 파싱(parse) 해야 JS/Python 객체 등으로 처리 가능
핵심 포인트
| 단계 | JS 객체 -> 서버 | 서버에서 처리 |
| 클라이언트 | JSON.stringify(obj) → JSON 문자열 | - |
| 서버 | - | JSON.parse(body) → 서버 언어 객체 |
| 이유 | HTTP Body는 문자열만 전송 가능 | 문자열로 받은 JSON을 코드에서 객체처럼 사용하려면 파싱 필요 |
💡 결론
- POST 요청 데이터는 Body에 담는 것이 표준
- 쿼리스트링도 가능하지만, 보안/용량 문제 때문에 비추천
- JS 객체를 그대로 보내는 게 아니라 JSON 문자열로 변환 후 전송
- 서버에서 문자열(JSON) → 객체(JS/Python) 로 변환하는 과정을 파싱이라고 함
Q. 이미 Content-Type 헤더로 json 으로 보내겠다고 설정했는데 body에 JSON.stringify() 로 JSON으로 직렬화할 필요가 있어? 그냥 body에 바로 JSON 문자열 넣어서 보내버리면 안 됨? body: {"name":"Alice","password":1234} 이런식으로
아, 이 부분은 JS 객체와 JSON 문자열 개념이 섞여서 헷갈리기 쉬운 부분이에요. 😄
1️⃣ body에 바로 JS 객체를 넣으면?
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: { name: "Alice", password: "1234" } // ❌ JS 객체
})
- ❌ 안 됨!
- fetch는 body를 문자열, FormData, Blob 등만 전송 가능
- JS 객체를 그대로 넣으면 에러가 나거나, 브라우저가 자동으로 문자열로 변환하지 않음
2️⃣ body에 JSON 문자열을 직접 넣으면?
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: '{"name":"Alice","password":"1234"}' // ✅ JSON 문자열
})
- ✅ 가능합니다.
- JSON.stringify()는 JS 객체 → JSON 문자열 변환을 도와주는 도구일 뿐
- 즉, 개발자가 직접 문자열로 작성해도 되고, JSON.stringify(obj)로 자동 변환해도 됩니다.
3️⃣ 요약
| 방법 | 설명 | 가능 여부 |
| JS 객체 그대로 | { name: "Alice" } | ❌ 안 됨 |
| JSON.stringify(obj) | JS 객체 → JSON 문자열 | ✅ 권장 |
| JSON 문자열 직접 작성 | '{"name":"Alice"}' | ✅ 가능 |
💡 결론
- Content-Type: application/json만 설정한다고 JS 객체가 자동으로 JSON 문자열로 변환되지 않아요.
- body에는 반드시 문자열 형태로 전달해야 함
- JSON.stringify()는 편하게 자동으로 문자열로 바꿔주는 방법
Q. body에 JSON 문자열 직접 넣는게 훨씬 간단할 거 같은데, 왜 body에 JSON.stringify(객체) 넣어서 변환하는 걸 더 권장한다는 거야?
1️⃣ JSON 문자열 직접 작성 vs JSON.stringify()
(1) JSON 문자열 직접 작성
body: '{"name":"Alice","password":1234}'
- 장점: 바로 문자열 넣어서 간단
- 단점:
- 타이핑 오류 발생 가능
- "password" 대신 'password' 쓰면 JS에서는 오류, JSON 표준에서는 큰따옴표만 허용
- 쉼표 하나 잘못 찍어도 문법 오류
- 동적 데이터 처리 불편
- 변수로 name/password 값 넣을 때 문자열 안에 ${}로 넣어야 함 → 번거롭고 실수 가능
- 타이핑 오류 발생 가능
// 동적 값 넣기 번거로움
body: '{"name":"' + userName + '","password":"' + userPwd + '"}'
(2) JSON.stringify(obj) 사용
body: JSON.stringify({ name: userName, password: userPwd })
- 장점:
- 자동 변환: JS 객체를 JSON 문자열로 안전하게 변환
- 동적 데이터 처리 편함: 변수, 함수 반환값 등 바로 객체 속성에 넣으면 됨
- 문법 오류 방지: 따옴표, 쉼표 등 JSON 문법 신경 쓸 필요 없음
- 유지보수 용이: 필드 추가/삭제 시 문자열 수정할 필요 없음
2️⃣ 결론
- 작은 하드코딩 JSON → 직접 작성 가능
- 실무/동적 데이터/유지보수 → JSON.stringify(obj) 권장
💡 한 줄 요약:
JSON.stringify는 "JS 객체 → JSON 문자열" 변환을 안전하게 자동으로 해주기 때문에, 동적/대규모 데이터 처리에서 실수를 줄이고 코드 유지보수가 편리해서 권장합니다.
Q. body에 JS 객체 넣어서 JSON.stringify(객체) 안 하고 JSON 문자열을 직접 넣을 경우, 변수로 name/password 값 넣을 때 문자열 안에 ${} 로 넣어야 된다고 했잖아. 이거에 대해서 알기쉽게 설명해줘
이 부분은 JS 객체 → 문자열 → 변수 넣기 과정에서 헷갈리는 사람들이 많아요. 쉽게 풀어서 설명해드릴게요.
1️⃣ 상황 정리
- 방법 1: JS 객체 → JSON.stringify()
const userName = "Alice";
const userPassword = "1234";
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: userName, password: userPassword })
});
- JS 객체를 그대로 넣으면 stringify가 자동으로 JSON 문자열로 변환 → 변수 값도 바로 들어감
- 방법 2: JSON 문자열 직접 작성
const userName = "Alice";
const userPassword = "1234";
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: '{"name": "${userName}", "password": "${userPassword}"}' // ❌
});
- ❌ 이렇게 쓰면 "${userName}" 문자열 그대로 전송됨, 변수값이 들어가지 않음
- 문자열 안에 ${}가 동작하려면 백틱(`)을 사용한 템플릿 리터럴이어야 함
2️⃣ 템플릿 리터럴 사용
const userName = "Alice";
const userPassword = "1234";
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: `{"name": "${userName}", "password": "${userPassword}"}`
});
- 백틱(`) 안에서 ${변수} 사용 → 변수 값이 문자열 안에 삽입됨
- 실제로 서버로 전송되는 문자열:
{"name": "Alice", "password": "1234"}
3️⃣ 차이점 요약
| 방법 | 장점 | 단점 |
| JSON.stringify(obj) | 자동 변환, 안전, 동적 데이터 처리 편함 | 별도의 함수 호출 필요 |
| 직접 문자열 + ${} | 바로 문자열 작성 가능 | 템플릿 리터럴 필요, 문법 실수 위험, 큰 데이터/중첩 객체는 번거로움 |
💡 핵심 포인트
- 작은 하드코딩 데이터: 템플릿 리터럴로 직접 문자열 가능
- 동적/대규모 데이터: JSON.stringify()가 안전하고 편리
- 단순히 큰따옴표로 문자열 감싸기만 하면 ${}는 변수 치환 안 됨!
Q. 백틱 & ${}
1️⃣ JSON 전체를 백틱(`)으로 감싸면?
- JS에서 문자열을 감쌀 때는 작은따옴표('), 큰따옴표("), 백틱(`) 모두 사용 가능
- 백틱으로 감싸도 JS 문자열로 취급
- 즉, JSON처럼 생겼다고 해도 JS 문자열일 뿐
const jsonString = `{"name":"Alice","age":23}`;
console.log(typeof jsonString); // "string"
- JSON 규칙(속성은 큰따옴표 등) 지켜야 서버가 parse 가능
- 단, 백틱 자체는 문자열 감싸는 용도 → JSON 문법과는 별개
2️⃣ 백틱(`)과 ${}
- 템플릿 리터럴: 백틱으로 만든 문자열
- ${} 안에 JS 표현식 넣으면 문자열 안에 값 삽입 가능
const name = "Alice";
const age = 23;
const msg = `Name: ${name}, Age: ${age}`;
console.log(msg); // "Name: Alice, Age: 23"
- ${} 안에는 변수뿐 아니라, 수식, 함수 호출 등 모든 JS 표현식 가능
const a = 5;
const b = 10;
console.log(`Sum: ${a + b}`); // "Sum: 15"
- 사용 시기
- 문자열 안에 동적 값 삽입할 때
- 여러 줄 문자열(\n 필요 없음) 작성할 때
3️⃣ JSON + 백틱 활용 예시
const userName = "Alice";
const userPassword = "1234";
// JSON 문자열 + 동적 값 삽입
const jsonString = `{"name": "${userName}", "password": "${userPassword}"}`;
- 실제 서버 전송되는 문자열:
{"name": "Alice", "password": "1234"}
- JSON.stringify()와 비교:
- JSON.stringify(): JS 객체 → 문자열 자동 변환
- 백틱 + ${}: 문자열 안에 변수 수동 삽입
✅ 요약
| 개념 | 설명 | 예시 |
| 백틱(`) | JS 문자열 감싸기, 템플릿 리터럴 | `Hello ${name}` |
| ${} | 백틱 안에서 JS 표현식 삽입 | `Age: ${age + 1}` |
| JSON 문자열 | 백틱 감싸도 JS 문자열, 서버에서 parse 필요 | `{"name":"Alice"}` |
백틱으로 감싸도 JSON 문자열 자체가 될 수 있고, 전송도 가능합니다. 단, 몇 가지 포인트가 있어요.
1️⃣ 백틱(`) = JS 문자열
- 백틱으로 감싸면 JS 문자열이 됩니다.
- JS 문자열 안에 JSON 규칙을 지키면 → 유효한 JSON 문자열이 됨
- 예시:
const userName = "Alice";
const jsonString = `{"name": "${userName}", "age": 23}`;
console.log(typeof jsonString); // "string"
- jsonString 자체는 JS 문자열이지만, 내부 내용은 JSON 문법을 지키고 있음
- 서버로 전송 가능:
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: jsonString
});
2️⃣ 중요한 포인트
- JS 문자열 vs JSON 문자열
- JS 문자열 = JS에서 취급하는 데이터 타입 string
- JSON 문자열 = JS 문자열 + 엄격한 JSON 문법
- 전송 가능 여부
- JS 문자열 안의 내용이 JSON 문법을 지키면 → 전송 가능
- JSON 규칙 안 지키면 → 서버에서 JSON.parse() 시 에러
- 템플릿 리터럴 장점
- ${}를 사용해 JS 변수값을 바로 삽입 가능
- 여러 줄 JSON 문자열도 편리하게 작성 가능
✅ 예시
const userName = "Alice";
const userPassword = "1234";
// 템플릿 리터럴로 JSON 문자열 작성
const jsonString = `{
"name": "${userName}",
"password": "${userPassword}"
}`;
fetch('/api/users', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: jsonString
});
- 서버에서는 정상적으로 JSON으로 인식 → JS 객체로 변환 가능
💡 요약
- 백틱으로 감싸도 JS 문자열일 뿐
- JSON 규칙을 지키면 유효한 JSON 문자열
- 서버로 전송 가능, JSON.stringify() 없이도 사용 가능
- ${} 사용 가능 → 변수 동적 삽입 편리
Q. 변수값을 넣지 말고 그냥 값을 JSON 문자열 안에 바로 넣으면 안됨? 굳이 귀찮게 const userName = "Alice" 이런식으로 변수 정의해서 변수로 넣는 이유가 뭐야?
이 부분은 정적 값 vs 동적 값 차이에서 이해하면 됩니다.
1️⃣ 정적 값만 있을 때
- 값이 항상 고정이라면 JSON 문자열 안에 바로 넣어도 문제 없음
const jsonString = `{
"name": "Alice",
"password": 1234
}`;
- 서버로 전송 가능 ✅
- fetch body에 바로 사용 가능
2️⃣ 변수로 넣는 이유 (동적 값 처리)
- 실제 애플리케이션에서는 대부분 값이 동적으로 바뀜
예시:
const userName = inputName.value; // 폼 입력 값
const userPassword = inputPassword.value;
const jsonString = `{
"name": "${userName}",
"password": "${userPassword}"
}`;
- 입력 값, 상태 값, 함수 반환 값 등 실시간 데이터를 JSON 문자열에 넣기 위해 변수 사용
- 매번 문자열을 수작업으로 바꾸는 것 불가능
3️⃣ 비교
| 방법 | 장점 | 단점 |
| JSON 안에 직접 값 | 코드 간단, 정적 값 전송 가능 | 값이 바뀌면 코드 수정 필요, 동적 처리 불가 |
| 변수 사용 + ${} | 동적 값 처리 가능, 재사용 편리 | 코드 약간 복잡, 템플릿 리터럴 필요 |
💡 결론
정적 값이면 바로 JSON 문자열 안에 넣어도 됨
- 사용자 입력, 서버 응답, 상태 값 등 동적 데이터는 변수로 넣는 게 필수
Q. JSON 엄격한 문법
1️⃣ JSON 속성(Property) 규칙
- 속성 이름 무조건 큰따옴표("")로 감싸야 함
❌ { name: "Alice" } → 잘못된 JSON (JS 객체는 가능)
✅ { "name": "Alice" } → 올바른 JSON
2️⃣ 값(Value) 규칙
body:
'{
"name": "Alice",
"password": 1234
"login": true,
"bookmark": { "concert": "bts", "sports": "lotte", "quantity": 2},
"array":[1,2,3]
}'
| 타입 | JSON에서 표기 | 예시 |
| 문자열(String) | 반드시 큰따옴표 | "Alice" ✅, 'Alice' ❌ |
| 숫자(Number) | 따옴표 없이 그대로 | 1234 ✅, "1234" ❌(문자열로 취급) |
| boolean | true / false | true, false ✅ |
| null | null | null ✅ |
| 배열(Array) | [] 안에 값 나열 | [1, 2, 3] ✅ |
| 객체(Object) | {} 안에 "속성":값 | { "age": 23 } ✅ |
3️⃣ 요약
- 속성 이름: 항상 " "
- 문자열 값: 항상 " "
- 숫자, boolean, null: 그대로
- JS 객체와 차이점: JS 객체는 'single quote'나 따옴표 없는 속성 이름도 가능하지만, JSON은 엄격하게 큰따옴표 사용
💡 한 줄 요약:
JSON은 데이터 교환 표준이라 엄격해서, 속성 이름과 문자열 값은 반드시 큰따옴표, 숫자/불리언/널은 따옴표 없이 표기해야 합니다.
Q. JSON 문자열에서 띄어쓰기는 선택 사항이에요.
1️⃣ 띄어쓰기와 줄바꿈
- JSON 문법상 띄어쓰기, 줄바꿈, 들여쓰기 등은 무시됨
- 예시:
{"name":"Alice","age":23}
vs
{
"name": "Alice",
"age": 23
}
- 둘 다 같은 JSON으로 서버에서 파싱됨
2️⃣ 왜 실무에서 띄어쓰기 없이 보내는가?
- 데이터 전송량 최소화 → 네트워크 효율 ↑
- 서버는 문자열 내부 공백 무시하고 파싱 → 기능에 영향 없음
💡 브라우저 개발자 도구나 API 문서에서는 보기 편하게 들여쓰기/줄바꿈 사용,
실제 요청에서는 최소화된 JSON 전송이 일반적
✅ 결론
- 띄어쓰기, 줄바꿈 넣어도 JSON 문법상 문제 없음
- 용량과 효율 때문에 보통은 제거
Q. 개발 단계에서는 보기 편하게 들여쓰기/줄바꿈하고, 실제 배포/빌드 단계에서 삭제하나?
거의 모든 개발 환경에서 그렇게 합니다.
1️⃣ 개발 단계
- 보기 편하게 들여쓰기/줄바꿈
- JSON, JS 객체, 코드 모두 사람이 읽기 편하게 작성
- 예시 (JSON):
{
"name": "Alice",
"age": 23,
"hobbies": ["reading", "coding"]
}
2️⃣ 배포/빌드 단계
- 최소화(minify) → 들여쓰기, 줄바꿈, 불필요 공백 제거
- 목적: 파일 크기 감소, 네트워크 전송 효율 향상
- 예시 (JSON):
{"name":"Alice","age":23,"hobbies":["reading","coding"]}
- JS 코드도 마찬가지로 minify → 변수명 축약, 공백 제거
3️⃣ 자동화
- 빌드 도구가 자동으로 처리
- 예: Webpack, Vite, Rollup 등 → JS/JSON 최소화 플러그인
- JSON 파일 자체는 JSON.stringify(obj)에서 JSON.stringify(obj, null, 2)처럼 들여쓰기 옵션 조절 가능
- 배포용 빌드에서는 자동으로 공백 제거 → 개발자는 신경 쓸 필요 없음
✅ 결론
- 개발 단계 → 보기 좋게 들여쓰기/줄바꿈
- 배포/실제 요청 → 최소화
- 빌드 도구가 대부분 자동 처리
Q. 변수명 축약은 어떤 식으로?
변수명 축약(minification)도 빌드/배포 과정에서 많이 쓰이는 최적화 기법이에요.
1️⃣ 개념
- 목적: 코드 크기 최소화 → 네트워크 전송 속도 향상
- 방법:
- 긴 변수명, 함수명을 짧게 바꾸기
- 예: userName → a / userPassword → b
- 빌드 도구가 자동으로 수행
2️⃣ 예시
원래 코드 (개발용)
function createUser(userName, userPassword) {
const newUser = { name: userName, password: userPassword };
console.log(newUser);
return newUser;
}
빌드 후 (minified)
function a(b,c){const d={name:b,password:c};console.log(d);return d}
- 변수명, 함수명 모두 짧게 변경 (userName → b, userPassword → c)
- 공백, 줄바꿈 제거
3️⃣ 특징
- 동작은 동일
- 읽기 어려움 → 개발용 소스와 구분 필요 → source map 사용
- 빌드 도구 사용
- Webpack + Terser
- Vite + esbuild
- Rollup + terser plugin
💡 요약
- 개발 중에는 읽기 쉽게 이름 → 배포 시 자동으로 짧게 축약
- source map으로 디버깅 가능
Q. source map으로 디버깅 한다는게 무슨 말이야 읽기 어렵게 삭제/축약된거 다시 개발용 코드로 복구해주단 말임?
맞아요! 😄 거의 정확합니다.
1️⃣ source map 개념
- minify/uglify → 코드가 짧게 축약되고 공백, 줄바꿈 제거 → 사람이 읽기 어려움
- source map = 축약/최적화된 코드 ↔ 원본 코드 매핑 파일
즉, 브라우저 개발자 도구에서 디버깅할 때:
- 실제 실행되는 minified 코드를 보고 있음
- 하지만 source map 덕분에 원본 코드 변수명, 줄 번호 그대로 표시 가능
- 브라우저 콘솔에서 break point, console.log 등 원본 코드 기준으로 디버깅 가능
2️⃣ 동작 예시
minified 코드
function a(b,c){console.log(b+c);return b+c}
- 읽기 어려움, 변수명 축약, 줄바꿈 없음
source map 적용 후 디버깅
- 브라우저에서 보면 원래 코드:
function addNumbers(num1, num2) {
console.log(num1 + num2);
return num1 + num2;
}
- 변수명과 줄 번호 그대로 보여짐 → 디버깅 편함
3️⃣ 특징
- 실제 배포 코드는 minified 상태
- source map 파일은 브라우저에서 디버깅용으로만 사용
- 배포 시 보안상 공개하지 않을 수도 있음
💡 요약
source map = 축약/최적화된 코드 ↔ 원본 코드 매핑표
디버깅 시, 읽기 어렵게 변환된 코드라도 원본 코드처럼 확인/브레이크포인트 가능
2️⃣ 티켓팅 웹사이트 예시
| 기능 | URL | HTTP 메서드 |
요청 Body | 응답 Body | 상태 코드 |
| 이벤트 목록 조회 | /api/events | GET | – | [ { "id": 1, "title": "Concert" }, ... ] | 200 OK |
| 이벤트 상세 조회 | /api/events/{id} | GET | – | { "id": 1, "title": "Concert", "date": "2025-10-20" } |
200 OK / 404 Not Found |
| 티켓 구매 | /api/events/{id}/tickets | POST | { "userId": 1, "quantity": 2 } |
{ "ticketId": 101, "eventId": 1, "quantity": 2 } |
201 Created / 400 Bad Request |
| 내 티켓 조회 | /api/users/{id}/tickets | GET | – | [ { "ticketId": 101, "eventId": 1, "quantity": 2 } ] |
200 OK |
3️⃣ 특징
- URL = 리소스 중심 (/users, /events, /tickets)
- HTTP 메서드 = 동작 지정 (GET, POST, PUT, DELETE)
- 요청 Body와 응답 Body를 명확히 정의 → 클라이언트가 어떤 데이터 보내고 받을지 정확히 알 수 있음
- 상태 코드 정의 → 성공, 실패, 권한 오류 등을 명확히 전달
실무에서는 이런 명세를 Markdown 문서로 관리하거나,
Swagger / OpenAPI 같은 도구를 써서 자동 문서화하고 테스트까지 할 수 있습니다.
Q. Swagger / OpenAPI
Swagger/OpenAPI 같은 도구가 “자동 문서화”라고 불리지만, 100% 자동으로 API 내용을 추측해서 만들어주는 건 아니에요. 대신 작성한 코드나 스펙을 바탕으로 문서를 자동으로 생성해주는 방식입니다.
1️⃣ Swagger/OpenAPI 구조
- 스펙 작성 방식
- YAML 또는 JSON 형식으로 API 명세를 작성
- 작성 내용 예시:
- 내가 작성해야 하는 것들
- URL (/api/users)
- HTTP 메서드 (POST, GET 등)
- 요청 Body 구조 (JSON 스키마)
- 응답 Body 구조 (JSON 스키마)
- 상태 코드, 설명 등
✅ 즉, 기능 자체나 구조를 직접 입력해야 함
2️⃣ 자동화되는 부분
- 코드 기반 문서화
- 예: Express, NestJS, Spring Boot 등에서 주석/데코레이터 기반으로 API 작성 → Swagger가 읽어서 문서 생성
- 예시 (Node.js + NestJS):
- 여기서 Swagger는 주석/데코레이터 정보를 기반으로 API 문서를 생성
- 즉, 코드 수정과 동시에 문서가 갱신 → 수작업으로 문서 따로 작성할 필요 줄어듦
3️⃣ 요약
| API URL / 메서드 | 수동 작성 필요 (코드나 스펙) |
| 요청/응답 Body 구조 | 수동 작성 (DTO/스키마) |
| 상태 코드 | 수동 작성 |
| 문서 생성 | Swagger/OpenAPI가 자동으로 시각화 + HTML 문서 생성 |
💡 핵심:
Swagger/OpenAPI는 문서를 자동으로 보여주고 테스트 가능하게 만들어줌
하지만 API 기능/URL/Body 구조 같은 핵심 정보는 개발자가 작성해야 함
Q. POST /api/users HTTP/1.1 (요청라인) & HTTP1.1 201 Created (응답라인) 어디서 확인? General 탭
1️⃣ HTTP 요청 구조
HTTP 요청(request)은 헤더(Header) + 바디(Body) 로 이루어져 있고,
요청 라인(Request Line)도 있어요.
예시 요청
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 52
{
"name": "Alice",
"email": "alice@example.com",
"password": "1234"
}
- POST /api/users → Request Line (요청 라인)
- 요청 메서드(POST) + URL(/api/users) + HTTP 버전(HTTP/1.1)
- Content-Type: application/json → 헤더(Header)
- Body → 실제 전송할 데이터(JSON)
즉, POST /api/users는 HTTP 요청 라인에서 볼 수 있음
2️⃣ HTTP 응답 구조
HTTP 응답(response)도 상태 라인(Status Line) + 헤더 + 바디 구조
예시 응답
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 48
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
- HTTP/1.1 201 Created → Status Line (상태 라인)
- HTTP 버전 + 상태 코드(201) + 상태 메시지(Created)
- Content-Type: application/json → 헤더(Header)
- Body → 실제 서버 데이터(JSON)
즉, HTTP/1.1 201 Created는 HTTP 응답 라인에서 확인
3️⃣ 브라우저 / 개발 도구에서 확인
- 크롬 개발자 도구(F12) → Network 탭
- 요청 클릭 → Headers 탭 확인
- Request URL / Request Method → POST /api/users
- Status Code → 201 Created
- Content-Type → application/json
- Response 탭 → body 확인
4️⃣ 요약
| 항목 | 위치 |
| POST /api/users | HTTP 요청 라인(Request Line) |
| HTTP/1.1 201 Created | HTTP 응답 라인(Status Line) |
| Content-Type: application/json | 요청/응답 헤더(Header) |
| { JSON 데이터 } | 요청/응답 Body |
💡 핵심:
- 요청 라인 / 응답 라인 = 메서드, URL, 상태 코드
- 헤더 = 전송 정보(콘텐츠 타입 등)
- 바디 = 실제 데이터
Q. 요청라인 / 응답 라인 안 보이는데?
실제로 크롬 개발자 도구(Network 탭 → Headers)에서 보면
**요청라인(Request Line), 응답라인(Status Line)**이 “Headers 섹션” 안에 같이 표시돼서 헷갈리실 수 있어요.
1️⃣ 원래 HTTP 메시지 구조
- 요청 메시지
POST /api/users HTTP/1.1 ← 요청 라인(Request Line)
Host: example.com ← 헤더(Header)
Content-Type: application/json
Content-Length: 52
{ "name": "Alice", "email": "alice@example.com" } ← 바디(Body)
- Request Method: POST
- Request URL: https://example.com/api/users
- 응답 메시지
HTTP/1.1 201 Created ← 상태 라인(Status Line)
Content-Type: application/json
Content-Length: 48
{ "id": 1, "name": "Alice" } ← 바디(Body)
- Status Code: 201 Created
즉, 요청라인/상태라인은 원래는 헤더랑 별도예요. (Header의 일부가 아님!)
2️⃣ 크롬 개발자 도구에서 왜 다 Headers에 보이냐?
개발자도구(Network 탭 → Headers)에 들어가면 이렇게 나오죠:
- General 섹션
- Request URL
- Request Method (POST)
- Status Code (201 Created)
- Remote Address
- Referrer Policy
- Request Headers 섹션
- Content-Type, Host, Authorization 등
- Response Headers 섹션
- Content-Type, Content-Length 등
즉, “요청라인/상태라인”은 General 섹션으로 분류돼 있고,
나머지 헤더는 각각 Request Headers / Response Headers로 구분돼서 보여줍니다.
3️⃣ 정리
- HTTP 원본 메시지
- Request Line / Status Line (맨 위 한 줄)
- Headers
- Body
- 개발자도구 표시 방식
- Request Line → General(Request Method, URL로 표시)
- Status Line → General(Status Code로 표시)
- Headers → Request Headers / Response Headers로 나눠 표시
- Body → Response 탭 or Payload 탭
👉 그래서 개발자 도구에서 요청라인/응답라인이 “Headers 탭”에 있는 것처럼 보이지만,
실제 HTTP 메시지 스펙에서는 헤더가 아니라 별도 라인이에요.
Q. 요청라인/응답라인이 General 탭 한군데 안에 같이 있네? 그리고 HTTP 버전은 없는데?
네, 아주 예리하게 보셨어요 👏
크롬 개발자도구(Network → Headers → General)에서는
원래 HTTP 메시지의 "라인"(요청라인, 상태라인)을 원문 그대로 보여주지 않고 쪼개서 가공된 형태로 보여줍니다.
1️⃣ 원래 HTTP 메시지 (표준 스펙)
- 요청(Request) 라인
POST /api/users HTTP/1.1
- 메서드: POST
- URL(path): /api/users
- HTTP 버전: HTTP/1.1
- 응답(Response) 상태 라인
HTTP/1.1 201 Created
- HTTP 버전: HTTP/1.1
- 상태 코드: 201
- 상태 메시지: Created
2️⃣ 크롬 개발자도구(General)에서 보이는 모습
General 섹션 예시:
Request URL: https://example.com/api/users
Request Method: POST
Status Code: 201 Created
Remote Address: 203.0.113.5:443
Referrer Policy: strict-origin-when-cross-origin
- Request Method → 요청 라인의 "POST"
- Request URL → 요청 라인의 URL (/api/users) + Host 헤더 합쳐서 보여줌
- Status Code → 응답 상태라인의 "201 Created"
- HTTP 버전은 따로 안 보여줌 (크롬이 단순화해서 감춰버린 것)
3️⃣ 중요한 점
- 실제 HTTP 원문은 Request Line / Status Line이 딱 한 줄이에요.
- 개발자도구는 이걸 분해해서 Request Method / Request URL / Status Code 같은 항목으로 나눠서 보여주는 것.
- 그래서 요청라인/응답라인이 합쳐져 보이는 것처럼 느껴지는 건, General 탭이 그걸 재구성해서 보여주기 때문이에요.
- HTTP 버전은 사실 존재하지만(HTTP/1.1, HTTP/2 등) → 크롬 DevTools에서는 기본적으로 안 보여줍니다. (Wireshark나 curl -v 같은 툴 쓰면 확인 가능)
✅ 정리
- HTTP 원본: Request Line + Headers + Body / Status Line + Headers + Body
- DevTools General 탭: Request Line & Status Line을 분해해서 재구성 → “Method, URL, Status Code”만 보여줌
- HTTP 버전은 생략 (HTTP/1.1, HTTP/2 같은 건 다른 툴에서 확인 가능)
Q. Remote Address / Referrer Policy ?
크롬 개발자도구 Network → Headers → General 안에 있는
- Remote Address
- Referrer Policy
이 두 항목은 HTTP 원문에는 없는, 브라우저가 추가로 보여주는 정보예요. 하나씩 설명해드릴게요.
1️⃣ Remote Address
Remote Address: 203.0.113.5:443
- 의미: 요청을 보낸 실제 서버의 IP 주소 + 포트 번호
- 즉, 브라우저가 최종적으로 TCP 연결을 맺은 대상
- 여기서 :443은 HTTPS의 기본 포트 번호 (HTTP는 보통 :80)
💡 예시
- https://example.com/api/users 로 요청 → DNS 조회 → example.com이 203.0.113.5로 해석
- 최종적으로 203.0.113.5:443 으로 요청 전송
- 이게 Remote Address로 표시됨
👉 즉, Remote Address는 “이 요청이 진짜로 간 서버”의 네트워크 주소
2️⃣ Referrer Policy
Referrer Policy: strict-origin-when-cross-origin
- 의미: 이 요청을 보낼 때, 브라우저가 "Referer 헤더"를 어떻게 포함시킬지 정하는 정책
- Referer 헤더 = 현재 페이지의 주소 (예: 사용자가 요청을 보낸 출처)
주요 Referrer Policy 값
- no-referrer → 아예 Referer 헤더를 안 보냄
- origin → 도메인만 보냄 (https://example.com 까지만, 경로 제외)
- origin-when-cross-origin → 같은 출처(origin)에서는 전체 URL, 다른 출처면 도메인만
- strict-origin-when-cross-origin (기본값, 최신 브라우저)
- 같은 출처 요청 → 전체 URL 보내줌
- 다른 출처 요청 → 도메인만 보내줌
- HTTPS → HTTP로 다운그레이드 요청할 때는 Referer 아예 안 보냄
💡 예시
내가 https://myapp.com/page에서
https://api.myapp.com/data 요청 → 같은 출처라 전체 URL이 Referer에 포함
https://other.com/data 요청 → 다른 출처라 Referer에는 https://myapp.com까지만 포함
👉 즉, Referrer Policy는 개인정보 보호와 보안을 위해 Referer 헤더에 얼마만큼 정보를 담을지 제어하는 규칙
3️⃣ 요약
- Remote Address = 실제로 연결된 서버의 IP:Port (브라우저가 DNS로 해석한 결과)
- Referrer Policy = 브라우저가 Referer 헤더를 어떻게 넣을지 결정하는 정책 (보안/프라이버시 관련)
Q. origin-when-cross-origin vs strict-origin-when-cross-origin 차이점
둘 다 비슷하지만 **차이는 “HTTPS → HTTP 다운그레이드 요청에서 어떻게 하느냐”**예요.
1️⃣ origin-when-cross-origin
- 같은 출처 요청: 전체 URL 보냄
- 다른 출처 요청: origin만 보냄 (https://example.com)
- HTTPS → HTTP 다운그레이드 시: 그래도 origin은 보냄
2️⃣ strict-origin-when-cross-origin (요즘 기본값)
- 같은 출처 요청: 전체 URL 보냄
- 다른 출처 요청: origin만 보냄
- HTTPS → HTTP 다운그레이드 시: 아예 Referrer를 안 보냄 🚫
✅ 차이 정리
| 상황 | origin-when-cross-origin | strict-origin-when-cross-origin |
| same-origin 요청 | 전체 URL | 전체 URL |
| cross-origin 요청 | origin만 | origin만 |
| HTTPS → HTTP 요청 | origin 보냄 | 안 보냄 |
즉 **strict-***가 더 "엄격"한 이유는 → 안전하지 않은 HTTP로 다운그레이드될 때 아예 Referrer를 차단해서 개인정보 유출 위험을 막기 때문이에요.
Q. Remote Address에서, Remote 란?
Remote Address에서 remote가 붙은 이유는 네트워크 용어 때문이에요.
1️⃣ 네트워크 관점에서 "remote"
- 네트워크에서는 내 컴퓨터(local) 와 상대방 컴퓨터(remote) 를 구분합니다.
- 내 컴퓨터에서 보는 관점 → 내가 직접 컨트롤하는 주소는 local address,
내가 접속한 상대편 주소는 remote address 라고 부릅니다.
💡 예시
- 127.0.0.1:8080 → 로컬(local) 주소 (내 컴퓨터 안에서 도는 서버)
- 203.0.113.5:443 → 리모트(remote) 주소 (인터넷 어딘가 원격 서버)
2️⃣ 왜 "Remote Address"라고 표시하냐?
브라우저 입장에서는:
- 요청을 보낸 "최종 목적지 서버"가
내 로컬 머신이 아닌, 네트워크상의 다른 컴퓨터이기 때문에 remote라고 불러요. - 이 용어는 TCP/IP 소켓 프로그래밍에서 그대로 따온 거예요.
TCP 연결 구조:
- Local Address: 내 IP + 내 포트
- Remote Address: 연결된 상대방의 IP + 포트
3️⃣ 정리
- remote = "원격" = 네트워크 연결 상대방
- Remote Address = "브라우저가 최종적으로 TCP 연결을 맺은 원격 서버 주소"
👉 정리하면, 여기서 remote는 "멀리 있는"이라는 일반 영어 의미보다는, 네트워크 통신에서 내(local)와 상대(remote)를 구분하는 기술 용어예요.
Q. 8080 포트란?
8080 포트는 웹 개발에서 정말 자주 보이는 번호인데,
이건 단순히 숫자 하나가 아니라 **네트워크에서 "문을 여는 번호"**를 의미해요.
1️⃣ 포트(Port)란?
- 한 컴퓨터(특히 서버)는 하나의 IP 주소를 갖고 있어요.
- 하지만 그 안에서 동시에 여러 프로그램(서비스)이 네트워크 요청을 받을 수 있죠.
- 그래서 "어떤 프로그램에 요청을 보내야 할지"를 구분하려고 포트 번호(0 ~ 65535) 를 사용합니다.
💡 비유
- IP 주소 = 아파트 주소
- 포트 번호 = 아파트 안의 호수 (몇 호 방문해야 하는지)
2️⃣ 8080 포트의 의미
- 원래 HTTP 기본 포트는 80번이에요. (http://example.com → 내부적으로 :80 사용)
- HTTPS 기본 포트는 443번 (https://example.com → 내부적으로 :443 사용)
- 도메인 example.com의 IP 서버 주소 203.0.113.5:443 에서 여러 서비스가 있는데,
- 그 중 HTTP 요청 서비스(80) / HTTPS 요청 서비스(443)에 요청 보낸다
근데 기본 포트는 이미 웹 서버(Apache, Nginx 같은 상용 서버)가 쓰고 있거나, 관리자 권한이 필요한 경우가 많아요.
→ 그래서 개발 환경에서 주로 대체 포트로 8080을 씁니다.
즉, 8080은 "HTTP 대체 포트"로 사실상 관습처럼 자리 잡은 번호예요.
3️⃣ 언제 보냐?
- 로컬 개발 서버 실행:(Node.js, Spring Boot, Tomcat 등이 기본값으로 8080 씀)
http://localhost:8080
- 클라우드 서비스나 프록시 설정에서, 80번 대신 8080 포트 열어둔 경우
4️⃣ 요약
- 포트 = 서버에서 실행 중인 특정 프로그램을 식별하는 번호
- 80 = HTTP 기본 포트
- 443 = HTTPS 기본 포트
- 8080 = 주로 HTTP 개발용 대체 포트 (80이랑 비슷해서 관습적으로 사용)
Q. 8080은 HTTP 개발용 대체 포트인데, HTTPS 개발용 대체포트는 없나?
공식적으로 정해진 HTTPS 전용 대체 포트 번호는 없어요. 하지만 관습적으로 몇 가지 자주 쓰이는 번호들이 있습니다.
1️⃣ 공식 기본 포트
- HTTP → 80
- HTTPS → 443
2️⃣ 관습적 대체 포트
- HTTP 대체 포트 → 8080 (제일 유명)
- HTTPS 대체 포트 → 보통 8443을 많이 씁니다.
왜 8443?
- 443 (HTTPS 기본 포트) 앞에 8을 붙인 것 → "8080처럼 대체 포트 느낌"
- 톰캣(Tomcat), 스프링부트(Spring Boot) 같은 서버 프레임워크가 HTTPS 테스트 서버 띄울 때 기본값으로 8443을 종종 씀
3️⃣ 임의 포트 가능
- 사실 HTTP든 HTTPS든 어떤 포트든 쓸 수 있어요 (0~65535 중 사용 가능한 번호면 됨).
- 다만 클라이언트에서 접속할 때 반드시 포트를 명시해야 함.
예:
https://example.com:8443
https://example.com:10443
✅ 정리
- 공식 대체 포트는 없음.
- HTTP → 8080, HTTPS → 8443 이 사실상 표준처럼 널리 사용됨.
- 다른 번호도 가능하지만 관습을 따라야 다른 개발자들이 이해하기 편함.
Q. 포트 번호
포트 번호는 네트워크에서 "어떤 서비스에 요청을 보낼지 구분하는 번호"인데,
그러면 왜 8080 같은 "대체 포트"를 마음대로 쓸 수 있는지,
localhost:8080이 뭘 의미하는지 헷갈릴 수 있어요. 차근차근 풀어드릴게요.
1️⃣ 포트 번호는 "문" 같은 것
- 하나의 컴퓨터(IP) 안에는 수많은 네트워크 서비스가 동시에 실행될 수 있어요.
- 그럼 서비스별로 문을 나눠야 어떤 프로그램이 요청을 받아야 하는지 알 수 있겠죠?
- 그래서 TCP/UDP는 0~65535 개의 포트 번호라는 "문"을 제공합니다.
비유:
- IP 주소 = 건물 주소
- 포트 번호 = 건물 안의 방문(호수)
- 손님(요청)이 왔을 때 몇 호로 가야 하는지 알려주는 게 포트예요.
2️⃣ 규칙(Well-known ports) vs 자유(Registered/Dynamic ports)
- 0 ~ 1023 (Well-known Ports)
→ 표준 서비스에 예약됨.- 80: HTTP
- 443: HTTPS
- 22: SSH
- 25: SMTP (메일)
이 범위는 운영체제가 관리자 권한(root)만 열 수 있음.
- 1024 ~ 49151 (Registered Ports)
→ 특정 앱/서비스가 관습적으로 사용 (예: 3306 = MySQL, 5432 = PostgreSQL).
- 49152 ~ 65535 (Dynamic/Ephemeral Ports)
→ 클라이언트가 요청 보낼 때 임시로 잡히는 포트.
👉 즉, "몇 번 포트는 반드시 이 서비스!"라는 건 0~1023 일부만 강제예요.
나머지는 서버/개발자가 "아무 포트나" 골라서 씁니다.
3️⃣ 그럼 localhost:8080은 뭐냐?
- localhost = "내 컴퓨터 자신" (loopback 주소, 127.0.0.1)
- 8080 포트 = 내가 직접 개발용 서버 프로그램을 띄운 포트
즉 http://localhost:8080은
👉 "내 컴퓨터에서 8080번 문을 열고 대기 중인 프로그램(예: Node.js 서버, Spring Boot 서버)에게 HTTP 요청을 보내라"는 뜻입니다.
이게 "내 인터넷을 켠다"는 게 아니라,
내 컴퓨터 안에서 네트워크 문(포트)을 열고, 거기서 서버 프로그램이 요청을 듣고 있는 상태를 말하는 거예요.
4️⃣ 왜 8080 같은 "대체 포트"를 쓰냐?
- 80번 포트는 원래 운영체제 권한 필요 + 이미 Apache/Nginx 같은 서비스가 쓰고 있을 확률이 높음.
- 개발자가 충돌 없이 자유롭게 쓰려면 1024 이상 포트(보통 8080, 3000, 5000 같은 숫자)를 씀.
- 그래서 로컬 개발 서버는 관습적으로 8080, 3000, 5173(Vite), 4200(Angular) 같은 포트를 씁니다.
✅ 정리
- 포트 번호는 "한 컴퓨터 안에서 어떤 프로그램(서비스)로 요청을 보낼지 구분하는 번호".
- 0~1023: 유명 서비스 예약 (HTTP=80, HTTPS=443 등).
- 그 외는 개발자가 자유롭게 지정 가능.
- localhost:8080은 "내 컴퓨터에서 8080번 포트에 떠 있는 서버 프로그램"을 가리킴.
- "대체 포트" = 표준 포트(80/443) 대신 충돌 피하려고 관습적으로 쓰는 번호.
Q. origin(출처) 이라는게 https://api.myapp.com/data 에서 https://myapp.com 까지만 인 거야? 즉 도메인 주소만 말하는거야?
뒤의 path나 하위도메인 api 이런 거 포함하지 않고?
그리고 .com은 뭐라고 부르더라?
1️⃣ origin(출처)의 정확한 정의
웹에서 origin(출처) 은 세 가지 요소의 조합이에요:
- scheme (프로토콜) → http, https
- host (호스트/도메인) → example.com, api.myapp.com
- port (포트 번호) → :80, :443, :8080
즉 origin = scheme + host + port
👉 https://api.myapp.com:443 ← 이 전체가 origin이에요.
2️⃣ origin에 포함 ❌ 안 되는 것
- ❌ path (/data) → 포함 안 됨
- ❌ query string (?id=10) → 포함 안 됨
- ❌ fragment/hash (#section1) → 포함 안 됨
즉,
https://api.myapp.com/data?id=10#section1
의 origin은 → https://api.myapp.com (포트 생략 시 https 기본 443)
3️⃣ host와 subdomain 관계
- myapp.com → 도메인
- api.myapp.com → 서브도메인 포함 host (origin에서는 완전히 다른 출처로 취급됨)
👉 https://myapp.com 과 https://api.myapp.com은 다른 origin이에요.
4️⃣ ".com"은 뭐냐?
- .com은 최상위 도메인(TLD, Top-Level Domain)
- myapp.com 전체를 도메인 이름(domain name)
- 그 앞에 붙는 api. 같은 건 서브도메인(subdomain)
✅ 정리
- origin = scheme + host + port (경로, 쿼리, 해시는 제외)
- https://api.myapp.com/data 의 origin = https://api.myapp.com
- .com = 최상위 도메인(TLD)
Q. myapp만은 뭐라고 함?
도메인 이름 구조
myapp.com을 쪼개보면:
- myapp → Second-Level Domain (SLD, 2차 도메인)
- .com → Top-Level Domain (TLD, 최상위 도메인)
- myapp.com → Domain name (전체 도메인 이름)
예시
- google.com
- google = SLD (2차 도메인)
- .com = TLD
- openai.org
- openai = SLD
- .org = TLD
서브도메인까지 포함하면
- api.myapp.com
- api = Subdomain (서브도메인)
- myapp = SLD (2차 도메인)
- .com = TLD (최상위 도메인)
✅ 정리
- .com → TLD
- myapp → SLD (2차 도메인)
- myapp.com → 전체 도메인 이름
- api.myapp.com → 서브도메인 + 도메인 이름
1️⃣ origin 정의 복습
웹에서 origin(출처)은 3가지 요소로 정의됩니다:
- scheme → http / https
- host → 호스트 이름(서브도메인 포함)
- port → 포트 번호
origin = scheme + host + port
path, query, fragment 등은 origin에 포함되지 않음
2️⃣ https://api.myapp.com/data 의 origin
- scheme → https
- host → api.myapp.com (서브도메인 포함!)
- port → 생략 → 기본 https 포트 443
따라서 origin = https://api.myapp.com
3️⃣ 서브도메인 포함 여부 중요
- https://myapp.com 과 https://api.myapp.com 은 host가 다르므로 origin도 다름
- 그래서 브라우저는 CORS 정책에서 서로 다른 출처(cross-origin) 로 취급
즉, path /data는 origin 계산에 포함되지 않지만, host(서브도메인 포함) 는 origin 계산에 반드시 포함됨
✅ 핵심
- origin은 scheme + host(서브도메인 포함) + port
- https://api.myapp.com/data → origin = https://api.myapp.com
- https://myapp.com → origin = https://myapp.com → 서로 다른 origin
Q. Referrer Policy 정리
1️⃣ Referrer Policy: strict-origin-when-cross-origin 기준
- 같은 origin → 전체 URL 그대로 전달
- 다른 origin → origin만 전달 (scheme + host + port)
- HTTPS → HTTP → 아예 Referrer를 전달하지 않음
2️⃣ 예시 적용
(1) 현재 페이지:
https://api.myapp.com/userLogin?id=name&password=123#section1
- 요청 받는 서버:
https://api.myapp.com/success
- origin 비교: https://api.myapp.com vs https://api.myapp.com → 같은 origin
- Referrer 헤더: 전체 URL 포함
Referrer: https://api.myapp.com/userLogin?id=name&password=123
#section1 (fragment)은 브라우저에서 Referrer에 포함 안 됨
실제 Referrer는 https://api.myapp.com/userLogin?id=name&password=123
(2) 요청 받는 서버:
https://api.yourapp.com/index.html
- origin 비교: https://api.myapp.com vs https://api.yourapp.com → 다른 origin
- Referrer 헤더: origin만 전달
Referrer: https://api.myapp.com
(3) 요청 받는 서버:
https://myapp.com/home`
- origin 비교: https://api.myapp.com vs https://myapp.com → 다른 origin (서브도메인 포함)
- Referrer 헤더: origin만 전달
Referrer: https://api.myapp.com
✅ 핵심 정리
- origin = scheme + host + port
- 같은 origin → 전체 URL 전달 (fragment 제외)
- 다른 origin → origin만 전달
- HTTPS → HTTP 요청 → Referrer 아예 없음
'프로그래밍' 카테고리의 다른 글
| 인증, 권한 관리 (0) | 2025.10.10 |
|---|---|
| 인증, 권한 관리 (0) | 2025.10.10 |
| DB 설계 :: DBSM :: SQL -> Query :: CRUD (0) | 2025.10.10 |
| 백엔드 API 명세 설계 예시 (0) | 2025.10.09 |
| fetch API 의 응답 분석 :: response, data 의 정체는? (0) | 2025.10.08 |
| 엔드포인트 란? API 호출과의 연관성 (0) | 2025.10.08 |
| 이스케이프 문자 란? 이스케이프 문자 종류 \" \n \r\n (줄바꿈) ... (0) | 2025.10.08 |
| JS 객체 vs JSON 문법 규칙 차이 :: " " (0) | 2025.10.08 |