[JS] fetch로 시작하는 웹 통신 기초 정리
목차
2021-03-21 공부 기록. fetch를 중심으로 웹 통신의 기초 개념을 정리한 노트입니다.

홈페이지에 요청을 보내고 그 결과를 출력하는 코드입니다. 실행하면 자바스크립트가 동작하고, 서버로부터 받은 응답(response)의 내용을 화면에 출력해 줍니다.
fetch — 서버에 요청을 보내고 응답을 받는 함수
fetch는 서버에 리퀘스트(request)를 보내고 리스폰스(response)를 받아오는 함수입니다. 오늘날 가장 보편적으로 쓰이는 방식이며, 다른 방법은 뒤에서 다룹니다. fetch 함수는 파라미터로 넘어온 URL로 리퀘스트를 보냅니다.

서버로부터 response를 받으면, 그 아래의 response.text()가 실행되고 그 결과가 response라는 객체에 저장됩니다.
이렇게 fetch 함수가 실행될 때 바로 실행되는 것이 아니라, 어떤 조건(응답이 도착함)을 만족시켜야 실행되는 함수를 콜백(callback) 함수라고 합니다.
then 메서드와 Promise
.then메서드는 콜백을 등록해 주는 메서드입니다.- fetch 함수가 리턴하는 객체의 메서드이며, 이 객체를 Promise 객체라고 합니다.
- Promise의 then 메서드로 등록한 콜백은, 서버로부터 리스폰스가 왔을 때 실행됩니다.
- 아래쪽 then은 위쪽 then 메서드가 실행된 후에 실행됩니다.

여기서 중요한 점은 이전 함수의 리턴값을 다음 함수가 이어받을 수 있다는 것입니다.
fetch('https://www.google.com')
.then((response) => response.text())
.then((result) => { console.log(result); });
fetch 함수의 실행 원리를 다시 정리하면 다음과 같습니다.
- fetch 함수는 어떤 객체(Promise 객체, 챕터 3에서 학습)를 리턴합니다.
- 이 객체의 then 메서드로 ‘리스폰스가 왔을 때 실행할 콜백’을 등록할 수 있습니다.
- 등록된 콜백들은 then 메서드로 등록한 순서대로 실행되고, 이전 콜백의 리턴값을 이후 콜백이 넘겨받아 사용할 수 있습니다.
왜 response를 바로 출력하지 않을까?
예리한 분들은 이런 의문이 들 수 있습니다.
fetch('https://www.google.com')
.then((response) => { console.log(response); });
‘이렇게 적어도 리스폰스의 내용을 출력할 수 있지 않나요?’
사실 이 response 파라미터로는 리스폰스의 실제 내용 자체가 넘어오는 게 아닙니다. response 파라미터에는 리스폰스에 관한 각종 부가 정보와 실제 내용을 함께 갖고 있는 **하나의 객체(object)**가 넘어옵니다. 그래서 원하는 실제 내용을 보려면 다음과 같이 해야 합니다.
fetch('https://www.google.com')
.then((response) => response.text())
.then((result) => { console.log(result); });
이렇게 response 객체의 text 메서드를 호출해야 합니다. 그리고 이 text 메서드의 리턴값이 바로 리스폰스의 실제 내용입니다.(사실은 실제 내용을 갖고 있는 Promise 객체이지만, 더 자세한 내용은 챕터 3에서)
이 코드를 완벽히 해석하려면 더 많은 내용이 필요하니, 지금은 이런 식으로 리스폰스를 받아서 처리할 수 있다는 정도만 기억하고 넘어갑시다.
개발자 도구(Developer Tools)
이전까지는 크롬 웹 브라우저의 ‘개발자 도구’에서 자바스크립트 코드를 실행했습니다. 개발자 도구에 관해 알아둘 사실들을 정리합니다.
1. 개발자 도구란?
웹 브라우저에 내장된 개발자 도구는, 브라우저가 내부적으로 어떤 동작을 하고 있는지 살펴보게 해 주는 도구입니다. 일반 사용자에게는 필요 없지만, 웹 프론트엔드 개발자에게는 매우 중요합니다. 자신이 작성한 코드를 브라우저가 어떻게 해석·실행하는지 자세히 볼 수 있기 때문입니다.
이번 토픽은 사용법 학습이 목적은 아니므로 별도로 다루지는 않습니다. 더 공부하고 싶다면 구글 공식 설명을 참조하세요. (영어가 부담되면 페이지 우측 상단 버튼으로 언어를 바꿀 수 있습니다.) 사용법을 몰라도 이번 토픽을 듣는 데는 지장이 없으니 안심하세요.
2. 개발자 도구를 여는 단축키
| 환경 | 단축키 |
|---|---|
| Mac | Command + Option + i |
| Windows | Ctrl + Shift + i |
자바스크립트를 실행하는 방법은 여러 가지지만, 이번 토픽에서는 빠르고 직관적인 결과 확인을 위해 개발자 도구에서 실행합니다. 자신의 환경에 맞는 단축키를 외워두세요.
3. 개발자 도구 위치 설정
처음 여는 경우 개발자 도구 창이 화면 오른쪽에 떠 있을 수 있습니다. 위치를 화면 하단 등으로 옮기고 싶다면, 개발자 도구 우측 상단의 점 세 개 아이콘을 클릭한 뒤, 위치 설정 탭에서 원하는 위치를 선택하면 됩니다.
4. Console 탭의 출력 원리
앞으로 개발자 도구의 Console 탭에서 자바스크립트를 실행하게 됩니다. 그런데 Console 탭의 출력 결과에는 알아둬야 할 점이 있습니다.

예를 들어 두 숫자를 더해 리턴하는 add 함수를 정의하고 엔터를 치면, 그 뒤에 undefined가 함께 출력됩니다. 이 undefined는 왜 나올까요?
add(1, 2)처럼 함수를 실행하면, 리턴값인3이 잘 출력됩니다.Hello라는 단어를 출력하면,Hello가 출력되고 그 밑에 또undefined가 출력됩니다.- 여러 줄을 연달아 작성하려면 Enter 대신 Shift + Enter를 사용합니다. add 함수를 여러 번 호출하면 가장 마지막 리턴값만 출력됩니다.
정리하면, Console 탭은 해당 코드가 최종적으로 리턴하는 값을 출력합니다. 아무 값도 리턴하지 않으면 undefined를 리턴한 것으로 간주합니다. 그래서
- add 함수를 선언하거나
- Hello 문자열을 출력하는 코드는
값을 리턴하는 코드가 아니므로 undefined가 출력됩니다. 반면 add(1, 2)처럼 실제로 값을 리턴하면 그 값 3이 출력됩니다. 여러 리턴값이 존재할 때는 가장 마지막 코드의 리턴값을 출력합니다.
앞으로 Console 탭에서
- 개발자 도구가 출력하는
undefined때문에 당황하거나 - 내 코드가 출력한
undefined와 개발자 도구의undefined가 섞여 혼란스러울 수 있는데,
위 원리를 기억하면서 결과를 해석하면 됩니다.
웹(Web)이란?

World Wide Web (거미줄, 연결망을 의미) → 전 세계적으로 연결된 통신망입니다. 웹 브라우저를 통해 돌아다니는 가상의 연결망을 의미합니다.
HyperText = 다른 텍스트를 참조하는 텍스트.

HTML이 바로 이 의미를 담고 있습니다.
URL이란?

URL은 웹에 존재하는 특정 데이터를 나타내는 문자열입니다.
호스트(host)

호스트는 특정 서버를 지칭합니다. 전 세계의 수많은 서버 중 하나의 도메인에 해당하는 것을 특정합니다.
경로(path)

경로(path)는 서버에 있는 데이터 중 원하는 데이터를 특정합니다. 서버마다 다르게 설정합니다. 예를 들어 ‘남성용 상품 중 셔츠’를 나타낼 수 있고, 다른 서버에서는 다르게 나타낼 수도 있습니다. 이건 개발자가 정하며, 이를 잘 설계하는 것이 개발자의 역할입니다. path는 서버 안의 데이터를 나타냅니다.
쿼리(query)

? 뒤의 부분은 쿼리(query)로, 데이터에 대한 세부적인 요구사항을 담습니다.

URL에 관한 궁금증 정리
URL은 크게 **호스트(host)·패스(path)·쿼리(query)**로 이루어져 있습니다. (다른 구성 요소도 있지만 일반적으로 보게 되는 것들만 다룹니다.)
1. 전 URL을 직접 입력한 적이 거의 없는데요?
웹 서핑을 할 때 보통 www.naver.com, www.google.com처럼 호스트 부분까지만 입력하고 메인 페이지로 진입합니다. 그 뒤로는 마우스로 이미지나 버튼을 클릭할 뿐, URL을 직접 입력할 일이 많지 않습니다.
그 이유는 클릭하는 버튼 등에 어느 URL로 새 리퀘스트를 보낼지 HTML/자바스크립트 코드로 이미 작성되어 있기 때문입니다. 예를 들어 버튼은 이런 HTML 코드로 작성되어 있습니다.
<a href="https://www.nazer.com/blogs/codeitOfficial/120"...>...</a>
<a href="/codeitCommunity/threads/731" ...>...</a>
(두 번째 URL은 상대 URL입니다. 같은 서버 안에 존재하는 데이터라면 path 이후의 부분만 표시할 수 있습니다.)
버튼을 클릭하면 a 태그의 href 속성에 적힌 URL로 브라우저가 알아서 리퀘스트를 보내고 리스폰스를 받아 새 페이지를 로드합니다. 이렇게 미리 세팅되어 있어서 사용자는 path·query의 형식을 알 필요가 없습니다. 의식하지 못한 채 계속 URL을 사용하고 있는 셈입니다.
하지만 개발자라면 path·query 설계를 직접 해야 하므로 URL 구조를 정확히 알아야 합니다.
2. 리퀘스트를 보내면 일어나는 일
주소창에 URL을 입력하고 엔터를 치면 다음 일들이 순차적으로 발생합니다. (왼쪽 아이콘 = 웹 브라우저, 오른쪽 = codeitshopping 서비스의 서버라고 가정)
- 브라우저는 URL의 호스트(host) 부분(
www.codeitshopping.com)을 보고, 전 세계 서버 중 어느 서버와 통신해야 하는지 찾습니다. 이때www.codeitshopping.com같은 것을 **도메인 네임(Domain Name)**이라고 합니다. 외우기 어려운 IP 주소 대신 외우기 쉬운 문자열로 서버를 나타낸 것입니다. 브라우저가 도메인 네임만으로 서버를 식별하려면 Domain Name Resolution 작업을 수행해야 합니다. - 통신할 서버를 식별하면, 브라우저는 그 서버로 리퀘스트를 보냅니다. 이때 URL에서 **path 이후 부분(path와 query)**을 리퀘스트에 담아 보냅니다.
- 리퀘스트를 받은 서버는 path 이후 부분을 보고, 그것이 의미하는 데이터를 찾아 리스폰스에 담아 보내줍니다.
- 브라우저는 받은 리스폰스의 내용을 사용자에게 보여줍니다. 내용이 HTML·자바스크립트 코드면 그에 맞는 화면을 그려서 보여줍니다. (리스폰스 내용의 다른 종류는 챕터 2에서)
URL을 기반으로 실제 리퀘스트가 어떻게 이루어지는지 감이 오시죠?
HTTPS란?
- 스킴(scheme) → 프로토콜의 이름
- 프로토콜(protocol) → 통신하는 두 주체가 지켜야 하는 통신 규약 (웹 브라우저와 서버가 지켜야 하는 규약)


웹 브라우저와 서버 사이의 리퀘스트와 리스폰스 둘 다 제대로 이뤄져야 합니다.

**HTTPS(HyperText Transfer Protocol Secure)**는 하이퍼텍스트를 전송하는 프로토콜(HTTP)에 보안성(Secure)을 더한 것입니다. 요즘은 텍스트뿐 아니라 이미지·소리·영상도 보내며, 보안성을 더해 끝에 s를 붙인 HTTPS를 더 많이 사용합니다.

한 페이지에 여러 번의 리퀘스트-리스폰스가 오간다
URL을 입력하고 엔터를 치면 보통 한 번 이상의 리퀘스트와 리스폰스가 오갑니다. 한 번이면 될 것 같은데 왜 그럴까요? 크롬 개발자 도구의 Network 탭으로 확인해 봅니다.

Network 탭은 브라우저가 구체적으로 어떤 리퀘스트를 보내고 어떤 리스폰스를 받는지 보여줍니다. 이 상태에서 새로고침을 해봅니다.

구글 메인 페이지로 재접속하면 Network 탭에 여러 줄(row)이 뜹니다. 각 줄 하나가 하나의 ‘리퀘스트-리스폰스’ 쌍입니다. 엔터 한 번에 무려 19번이나 되는 리퀘스트가 오갔습니다. 왜 이럴까요?
사실 첫 리스폰스의 내용만으로 온전한 화면을 그릴 수 있는 경우는 많지 않습니다. 보통은 첫 리스폰스에 적힌 ‘추가로 필요한 데이터’를 재차 요청해야 합니다. 예를 들어 첫 리스폰스로 이런 HTML이 왔다고 합시다.
<html>
<head></head>
<body>
...
<div>
<img src="https://www.server_A.com/a/b/exmaple.png".../>
</div>
...
<script src="http://www.server_B.com/c/d/main.js"></script>
</body>
</html>
브라우저는 이 코드만으로는 완벽한 화면을 그릴 수 없습니다. 추가로 가져와야 할 것들이 있기 때문입니다.
이 img 태그의 이미지를 그리려면
<img src="https://www.server_A.com/a/b/exmaple.png".../>
src 속성의 URL로 다시 리퀘스트를 보내 example.png를 받아와야 합니다.
그리고
<script src="http://www.server_B.com/c/d/main.js"></script>
이 script 태그의 URL로도 리퀘스트를 보내 main.js를 받아와야 합니다.
벌써 추가로 2번의 리퀘스트가 필요합니다. 이렇게 받은 리스폰스에 따라 또 새 리퀘스트가 필요할 수도 있습니다. 바로 이런 원리 때문에 한 페이지에 접속해도 그 사이에 수많은 리퀘스트와 리스폰스가 발생합니다.
Network 탭에서 한 줄을 클릭해 보면

https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=4340&directory=Untitled%202.png&name=Untitled+2.png
이렇게 Google 로고 이미지를 받기 위한 추가 리퀘스트가 오갔음을 확인할 수 있습니다. 특정 페이지에 접속할 때 보통 한 번 이상의 리퀘스트-리스폰스가 오간다는 사실을 기억하세요.
실습: ratings URL로 요청 보내기
URL: https://workey.codeit.kr/ratings
fetch('https://workey.codeit.kr/ratings')
.then((response) => response.text())
.then((result) => { console.log(result); });
개발자가 꼭 알아야 할 웹 개발
Web API

구글 홈페이지로 리퀘스트를 보내면 서버가 HTML과 자바스크립트로 이뤄진 내용을 리스폰스로 보내주고, 브라우저는 이 화면을 해석해 보여줍니다. 하지만 HTML·자바스크립트가 담긴 리스폰스 말고, 어떤 정보가 담긴 response도 있습니다.

기존 코드에서 URL 부분만 바꿔, 다른 종류의 response를 받아봅시다.


일반 HTML·JS가 아닌 **데이터 형식(객체 형식)**의 무언가가 콘솔에 출력됩니다. 이것을 JSON이라는 포맷으로 나타낸 것입니다.
앞서 구글 URL로 받은 리스폰스는 브라우저가 화면을 그리기 위해 필요한 정보였는데, 이번에는 순수하게 어떤 정보만을 담은 것을 response로 받았습니다. 개발자라면 이런 종류의 데이터를 받을 경우가 더 많습니다.
JSON(JavaScript Object Notation)
JSON은 정보를 교환하기 위해 만들어진 포맷으로, 스크립트 언어의 문법을 빌려서 만들어진 데이터 포맷입니다. 그래서 JSON의 문법은 자바스크립트의 문법과 비슷합니다.

이 사진은 사용자 한 명을 JSON 객체로 나타낸 것입니다.
- 중괄호를 쓰고 그 안에 각 프로퍼티 이름과 값을 쓰는 것은 자바스크립트의 객체 표기 방식과 같습니다.
- 이를 대괄호로 둘러싸서 배열로 나타내는 것도 자바스크립트와 동일합니다.
개발자로 일하면 JSON을 사용하는 경우가 많습니다. 하지만 자바스크립트와 JSON의 문법이 100% 동일하지는 않습니다.
자바스크립트 객체와 JSON의 차이
자바스크립트에서 객체·배열을 나타내는 문법이 그대로 JSON에서 쓰이지만, 완벽히 동일하지는 않습니다.
1. JSON에는 프로퍼티 이름·값 표현 방식에 제한이 있습니다.
(1) JSON에서는 각 프로퍼티의 이름을 반드시 큰따옴표(")로 감싸야 합니다.
자바스크립트로 member 객체를 생성해 봅시다.
const member = {
name: 'Michael Kim',
height: 180,
weight: 70,
hobbies: ['Basketball', 'Listening to music']
};
이렇게 중괄호 안에 키·밸류 쌍을 나열하는 표기를 Object Literal이라고 합니다. Object Literal에서는 프로퍼티 이름에 큰따옴표를 붙이지 않아도 되고,
const member = {
"name": 'Michael Kim',
"height": 180,
"weight": 70,
"hobbies": ['Basketball', 'Listening to music']
};
이렇게 붙여도 됩니다. 하지만 JSON에서는 반드시 큰따옴표를 붙여야 합니다.
{
"name":"Michael Kim",
"height":180,
"weight":70,
"hobbies":["Basketball", "Listening to music"]
}
각 프로퍼티 이름이 모두 큰따옴표로 둘러싸여 있죠? 감싸주지 않으면 JSON을 처리할 때 에러가 납니다.
(2) JSON에서는 값이 문자열인 경우 큰따옴표(")를 사용해야 합니다.
const member = {
"name": 'Michael Kim',
"height": 180,
"weight": 70,
"hobbies": ['Basketball', 'Listening to music']
};
자바스크립트에서는 문자열을 작은따옴표(')로 써도 되고 큰따옴표(")로 써도 됩니다. 하지만 JSON에서는 문자열 값을
{
"name":"Michael Kim",
"height":180,
"weight":70,
"hobbies":["Basketball", "Listening to music"]
}
"Michael Kim", "Basketball"처럼 항상 큰따옴표로 감싸야 합니다.
2. JSON에서는 표현할 수 없는 값들이 있습니다.
자바스크립트의 undefined, NaN, Infinity 등은 JSON에서 사용할 수 없습니다. JSON은 자바스크립트에서 비롯되었지만, 언어·환경에 종속되지 않고 언제 어디서든 사용할 수 있는 데이터 포맷을 목표로 만들어졌습니다. 따라서 자바스크립트 문법에서만 유효한 개념은 JSON으로 나타낼 수 없습니다.
3. JSON에는 주석을 추가할 수 없습니다.
JSON은 코드가 아니라 데이터 포맷이기 때문에 주석을 포함시킬 수 없습니다.
이 둘은 일반적으로 호환되는 것이 맞지만, 위 세부 차이를 알아두는 게 좋습니다. 모르면 나중에 JSON 데이터를 처리하다 에러가 생겨도 이유를 이해할 수 없기 때문입니다. 더 궁금하다면 MDN JSON 문서를 참조하세요.
JSON 데이터를 객체로 변경하기 (Deserialization)

리스폰스로 받은 이 부분은 String 타입입니다. typeof 연산자로 확인해 보면 string 타입이 나옵니다. response 부분에서 string 타입으로 넘겨주는 것입니다.

예를 들어 위 JSON에서 이름만 추출하고 싶을 때, string 상태로 문자열을 다룰 수도 있겠지만 그럴 필요가 없습니다. String 타입의 JSON은 바로 자바스크립트 객체로 변경할 수 있기 때문입니다.

JSON은 JSON 데이터를 다루기 위해 사용되는 기본 객체입니다. JSON 객체에 parse를 사용하면 string 타입의 객체를 자바스크립트 객체로 변환할 수 있습니다.

즉, 스트링 타입의 JSON 데이터에 표현된 사용자 정보 배열을 실제 자바스크립트 배열로 변환합니다. 그 변환한 결과를 users 객체에 넣었습니다.

배열의 각 요소를 순회하면서 파라미터로 받은 것을 실행합니다.

방금 말한 것처럼 스트링 타입의 JSON은 자바스크립트 객체로 변환해 줘야 합니다. 자바스크립트의 내장 객체인 JSON 객체의 parse 메서드를 사용했다는 점을 잊지 마세요.
실습: 초급 토픽만 추리기
코드잇에 입사한 태우는 모든 토픽 중 난이도가 ‘초급’인 토픽만 추려 평균 별점을 계산하는 업무를 맡았습니다. 아래 URL로 리퀘스트를 보내면 모든 토픽 정보를 받을 수 있습니다.
fetch('https://learn.codeit.kr/api/topics')
.then((response) => response.text())
.then((result) => { console.log(result) })
URL: https://learn.codeit.kr/api/topics
모범 답안
fetch('https://learn.codeit.kr/api/topics')
.then((response) => response.text())
.then((result) => {
const topics = JSON.parse(result);
const beginnerLevelTopics = topics.filter((topic) => topic.difficulty === '초급');
console.log(beginnerLevelTopics);
});
이 URL로 리퀘스트를 보내면 토픽 정보가 담긴 JSON 데이터가 옵니다.
[
{
"id":1,
"title":"업무 자동화 for Windows",
"keyword":"Python",
"difficulty":"초급"
},
{
"id":2,
"title":"업무 자동화 for Mac",
"keyword":"Python",
"difficulty":"초급"
},
{
"id":3,
"title":"프로그래밍 기초 in JavaScript",
"keyword":"JavaScript",
"difficulty":"초급"
},
...
]
이 JSON 데이터가 result 파라미터로 넘어올 때는 데이터 타입이 string이므로 자바스크립트 객체로 변환해야 합니다. 이를 위해 내장 객체 JSON의 parse 메서드의 파라미터로 해당 JSON 데이터를 넣고 실행하면 됩니다.
그러면 JSON 데이터에서 배열로 표현된 부분이 실제 자바스크립트 배열(객체)로 변환되어 자유롭게 사용할 수 있습니다. 이렇게 string 타입의 JSON 데이터를 자바스크립트 객체로 변환하는 것을 **Deserialization(역직렬화)**라고 합니다. 아주 중요한 개념이니 잘 기억해 두세요.
이후 코드는 topics 배열의 filter 메서드로 difficulty가 ‘초급’인 것들만 추출해 새 배열을 만들고 출력합니다. 개발자가 되면 리스폰스로 오는 JSON 데이터를 다룰 때, 보통 string 타입을 자바스크립트 객체로 만드는 Deserialization을 먼저 수행한다는 사실을 꼭 기억하세요.
리퀘스트의 종류와 메서드 (CRUD)
데이터를 보내기 위한 request 외에도 다른 성격의 request가 존재합니다. 서버에 어떤 데이터를 추가해 달라는 요청도 보낼 수 있습니다. 리퀘스트는 종류에 따라 4가지로 나눌 수 있는데, 이것이 곧 CRUD입니다.
- 서버의 데이터 조회
- 서버의 데이터 추가
- 서버의 데이터 수정
- 서버의 데이터 삭제
이 종류는 **리퀘스트의 메서드(method)**를 통해 알 수 있습니다.


Request의 Head와 Body
리퀘스트 메서드 값이 어떻게 설정되어 있는지 살펴봅시다. Request는 head와 body로 이뤄져 있습니다.

- Head: 부가 정보가 들어가며, 메서드가 head에 들어갑니다.
- Body: 실제 데이터를 담는 부분. 리퀘스트를 보낼 때 실 데이터는 body에 들어갑니다.

데이터를 추가하는 POST나 수정하는 PUT은 body에 넣어 보내줘야 하지만, 데이터를 가져오는 GET이나 삭제하는 DELETE 메서드에서는 body가 필요하지 않습니다.

콘솔에 이전 코드를 넣어 실행하면 이전처럼 response 내용이 잘 출력됩니다. 개발자 도구에서 Network 탭을 보면, 주고받은 리퀘스트와 리스폰스를 확인할 수 있고, Headers 부분을 클릭하면 리퀘스트 정보를 볼 수 있습니다.

**헤더(header)**란 head 안에 존재하는 하나하나의 쌍입니다. 각 헤더는 리퀘스트에 대한 부가 정보를 갖고 있고, 리퀘스트를 받는 서버가 알았으면 하는 정보를 담고 있습니다.

여기에 우리가 배운 메서드가 있습니다(method: get). 현재 리퀘스트가 데이터를 조회하려는 GET 리퀘스트임을 알 수 있습니다.
메서드 값은 리퀘스트의 헤더에 존재한다 (아주 중요)
- path 헤더: URL 구조를 다룰 때 봤던 패스 정보입니다. 패스 정보가 리퀘스트 헤더에 있으므로 브라우저가 무슨 정보를 원하는지 알 수 있습니다.
- user-agent 헤더: 리퀘스트를 보낸 브라우저와 OS를 알 수 있습니다. 서버에서 브라우저 정보를 수집한다면 이 헤더의 값을 사용합니다.
헤더는 종류가 많아 외우기 힘들고 그럴 필요도 없습니다. 필요할 때마다 검색을 통해 해결합시다. request body도 봐야 하지만, 지금은 데이터 존재 여부를 확인하는 GET 메서드라 body가 없으므로, POST나 PUT 메서드를 다룰 때 봐야 합니다.
4가지 리퀘스트 종류와 메서드 정리
| 작업 | 메서드 |
|---|---|
| 기존 데이터를 조회 | GET |
| 새 데이터를 추가 | POST |
| 기존 데이터를 수정 | PUT |
| 기존 데이터를 삭제 | DELETE |
각 리퀘스트 종류에 따라 리퀘스트의 head에 알맞은 메서드를 설정해야 합니다. 다음부터는 코드잇 학습용 URL을 사용해 직접 수행해 봅니다.
학습용 URL: https://learn.codeit.kr/api/members
진행할 작업은 다음과 같습니다.
- 전체 직원 정보 조회 - GET
- 특정 직원 정보 조회 - GET
- 새 직원 정보 추가 - POST
- 기존 직원 정보 수정 - PUT
- 기존 직원 정보 삭제 - DELETE
URL 유형 2가지
위 작업에 사용하는 URL 유형은 크게 2가지입니다.
(A) 특정 직원을 대상으로 하는 작업 — 작업 대상 직원을 특정하기 위해 URL 끝에 고유 식별자(직원 id)를 붙입니다.
- 특정 직원 정보 조회 - GET
- 기존 직원 정보 수정 - PUT
- 기존 직원 정보 삭제 - DELETE
https://learn.codeit.kr/api/members/3
이 URL은 3번 직원 정보에 대한 작업을 수행하겠다는 뜻입니다.
(B) 전체 직원을 대상으로 하는 작업 — 특정 직원이 아닌 전체를 대상으로 하므로 원래의 URL을 그대로 사용합니다.
- 전체 직원 정보 조회 - GET
- 새 직원 정보 추가 - POST
https://learn.codeit.kr/api/members
작업 종류에 따라 메서드뿐만 아니라 URL도 적절히 변경해 가며 써야 합니다.
GET / POST / PUT / DELETE 직접 보내기

이건 어떤 회사 직원의 정보를 나타내는 데이터입니다. 이 URL에 순서대로 PUT, GET, POST, DELETE를 보내봅시다.
GET — 조회
GET은 그대로 보냅니다.
fetch('https://learn.codeit.kr/api/members')
.then((response) => response.text())
.then((result) => { console.log(result); });

특정 직원만 조회하고 싶으면 members 뒤에 조건(id)을 더 넣어주면 됩니다.
fetch('https://learn.codeit.kr/api/members')
.then((response) => response.text())
.then((result) => { console.log(result); });
(예: id 3인 사원 브라이언만 출력)
POST — 추가
POST 리퀘스트는 추가할 사원 정보를 body에 넣어줘야 합니다. 새 직원 객체 newMember를 정의합니다.

URL 뒤에 새로운 객체(argument)가 설정되어 있는데, 이는 리퀘스트에 관한 새로운 설정을 해주는 객체입니다. 앞으로 이 객체를 옵션 객체라고 부릅니다.
const newMember = {
name:'Jerry',
email:'jerry@codeitmall.kr',
department:'engineering',
};
fetch('https://learn.codeit.kr/api/members',
{
method:'POST',
body:JSON.stringify(newMember),
})
.then((response) => response.text())
.then((result) => { console.log(result); });
GET 리퀘스트를 보낼 때는 이런 게 없었는데, fetch 함수의 디폴트가 GET이기 때문입니다. 그래서 GET이 아닌 다른 메서드를 보내려면 이런 옵션 객체가 필요합니다.
method: 리퀘스트의 메서드를 POST로 설정.body: 리퀘스트에 담을 body 부분을 설정.
그런데 newMember 객체를 바로 넣은 게 아니라, 앞서 배운 JSON.stringify를 거쳐 그 결과를 넣었습니다. 왜일까요?
예전에 string 타입의 JSON을 자바스크립트 객체로 변환하기 위해
parse메서드를 썼는데,stringify는 정반대 기능입니다.
stringify는 자바스크립트 객체를 string 타입으로 변환합니다. 어떤 자바스크립트 객체의 내용을 외부로 전송하고 싶으면 stringify로 타입을 변환해야 합니다. 자바스크립트 객체 자체로는 외부에 바로 전송이 안 되기 때문입니다.

id는 새 정보를 추가할 때 서버의 데이터베이스에서 새로 설정해 주는 값입니다. POST 리퀘스트를 하면 추가되는 결과가 다를 수 있으며, 어떤 리스폰스를 줄지는 개발자 설계에 따라 달라지므로 당연한 일입니다.

POST 리퀘스트를 보낼 때는 GET과 달리 fetch 함수의 두 번째 파라미터에 옵션 객체를 넣어줘야 했고, 옵션 객체에 메서드 값과 body 내용을 설정했다는 점을 잘 기억해 두세요.
Network 탭에서 POST 확인하기
POST 리퀘스트의 head와 body가 어떻게 생겼는지 개발자 도구의 Network 탭으로 확인해 봅니다.

새 직원 정보를 POST로 추가하고 Network 탭을 클릭합니다.

내가 보낸 POST 리퀘스트와 받은 리스폰스 쌍을 의미하는 줄(members)을 클릭합니다. 오른쪽 화면의 Request Method 부분에 POST라고 써 있습니다. 스크롤을 내려보면

Request Payload라고 쓰인 부분이 바로 리퀘스트의 body입니다. 새 직원 정보를 나타내는 JSON 데이터가 잘 들어가 있죠? GET 때는 없던 body를 POST에서는 볼 수 있습니다.
Response 탭을 눌러보면,

실제로 추가된 직원 정보가 JSON 데이터로 잘 온 것을 알 수 있습니다. 웹 통신을 할 때 이렇게 개발자 도구로 리퀘스트·리스폰스의 모습을 살펴보면 현재 무슨 일이 일어나는지 훨씬 실감나게 느낄 수 있습니다.
PUT — 수정
PUT 메서드로 수정을 해봅니다. id가 2인 사원 Alice의 부서를 수정하는 코드입니다.


const member = {
name:'Alice',
email:'alice@codeitmall.kr',
department:'marketing',
};
fetch('https://learn.codeit.kr/api/members/2',
{
method:'PUT',
body:JSON.stringify(Member),
})
.then((response) => response.text())
.then((result) => { console.log(result); });

값이 변경된 것을 확인할 수 있습니다.
DELETE — 삭제
DELETE 메서드도 확인해 봅니다.

fetch('https://learn.codeit.kr/api/members/2',
{
method:'DELETE',
.then((response) => response.text())
.then((result) => { console.log(result); });
DELETE 메서드는 삭제 요청이라 body가 필요 없습니다.

삭제되어 보이지 않습니다. 이러한 작업들을 위해 fetch 함수를 어떻게 사용했는지 잘 알아둡시다.
Web API와 REST API
웹 브라우저가 리퀘스트를 보낼 때
- 어느 URL로 보내는지
- 무슨 메서드(GET, POST, PUT, DELETE 등)가 head에 설정되어 있는지
가 중요하다는 것을 배웠습니다. 그런데 어떤 리퀘스트를 보냈을 때 무슨 리스폰스를 받는지는 어떻게 설계될까요?
1. Web API
리퀘스트에 대한 리스폰스 규격은 모두 서비스를 만드는 개발자들이 정합니다. 개발자에는 크게 두 종류가 있습니다.
- 프론트엔드(Front-end) 개발자: 사용자가 직접 보는 웹 페이지·앱 등을 만듦
- 백엔드(Back-end) 개발자: 리퀘스트를 받아 적절히 처리한 후 리스폰스를 주는 서버 프로그램을 만듦
하나의 서비스를 만들 때, 양측이 모여 ‘프론트엔드에서 이 URL로 이런 리퀘스트를 보내면, 백엔드에서 이렇게 처리하고 이런 리스폰스를 보내주자’와 같은 논의를 하고 개발을 시작합니다. 이것을 Web API 설계라고 합니다.
API란 Application Programming Interface의 약자로, 원래는 ‘개발할 때 사용할 수 있도록 라이브러리·플랫폼 등이 제공하는 데이터나 함수’를 의미합니다. 웹 개발에서는 어느 URL로 어떤 리퀘스트를 보냈을 때 무슨 처리가 수행되고 어떤 리스폰스가 오는지에 관해 미리 정해진 규격을 Web API라고도 합니다.
예를 들어 직원 정보 추가 기능을 설계한다면 다음처럼 할 수 있습니다.
...
3. 직원 정보 추가
https://learn.codeit.kr/api/members
(1) Request
- Head
Method : POST
...
- Body
{
"name": "Jerry",
"email: "jerry@codeitshopping.kr",
"department": "engineering",
}
...
(2) Response
Success인 경우 :
- Head
...
- Body
{
"id": "[부여된 고유 식별자 값]",
"name": "Jerry",
"email": "jerry@codeshopping.kr"
"department": "engineering",
}
Fail인 경우 :
...
이렇게 각 URL에 어떤 리퀘스트를 보내면 서버가 어떤 리스폰스를 보내야 하는지 일일이 설계하는 것이 Web API 설계입니다. (실무에서는 상용 툴 등으로 더 체계적으로 정리합니다.) 이렇게 설계되고 나면 프론트엔드/백엔드 개발자들이 설계에 맞게 각자 코드를 작성하기 시작합니다.
그런데 문제가 하나 있습니다. Web API는 어떻게 설계해도 동작에는 지장이 없다는 것입니다. 예를 들어 직원 정보 추가를
(1) 'https://learn.codeit.kr/api/members' URL로
(2) 리퀘스트의 헤드에 POST 메소드를 설정하고,
(3) 리퀘스트의 바디에 새 직원 정보를 넣어서 보낸다
처럼 설계할 수도 있지만, 어떤 회사는
(1) 'https://learn.codeit.kr/api/members' URL로
(2) 리퀘스트의 헤더에 GET 메소드를 설정하고,
(3) 리퀘스트의 바디에 새 직원 정보를 넣어서 보낸다
처럼 설계할 수도 있습니다. 어느 쪽이든 동작에는 문제가 없습니다. 하지만 기능에 문제가 없다고 아무렇게나 설계해도 되는 것은 아닙니다. Web API가 잘 설계되었는지에 관한 기준으로 보통 REST API가 사용됩니다.
2. REST API 이야기
REST API는 많은 웹 개발자들이 Web API 설계 시 준수하려 노력하는 가이드라인입니다. 이를 이해하려면 먼저 REST architecture가 무엇인지 알아야 합니다.
REST architecture란 미국의 컴퓨터 과학자 Roy Fielding이 박사 논문 ‘Architectural Styles and the Design of Network-based Software Architectures’에서 제시한 개념으로, 웹이 갖추어야 할 이상적인 아키텍처를 의미합니다. REST는 **Representational State Transfer(표현적인 상태 이전)**의 줄임말입니다. 웹을 하나의 거대한 프로그램이라고 보면, 각 웹 페이지는 그 프로그램의 내부 상태를 나타냅니다. 페이지를 옮겨 다니며 보게 되는 내용은 웹이라는 프로그램의 매번 새로운 상태를 나타내는 ‘표현’이라 할 수 있어 ‘표현적인, 상태 이전’이라고 합니다.
REST architecture의 6가지 조건
다음 6가지 기준을 충족하면 REST architecture로 인정됩니다.
- Client-Server: Client-Server 구조로 양측의 관심사를 분리합니다. (Client = 브라우저가 실행되는 컴퓨터, Server = 서비스를 제공하는 컴퓨터) 분리해 두면 Client는 더 나은 화면·다양한 기기 대응에, Server는 적합하고 확장 가능한 구조에 집중할 수 있고, 서로 독립적으로 운영될 수 있습니다.
- Stateless: Server는 각 리퀘스트에 관해 어떤 맥락(context)도 저장하지 않습니다. 즉, 매 리퀘스트는 독립적으로 취급되며, 리퀘스트에는 항상 필요한 모든 정보가 담겨야 합니다.
- Cache: Cache를 활용해 네트워크 비용을 절감합니다. Server는 리스폰스에 재활용 가능 여부(Cacheable)를 담아 보내야 합니다.
- Uniform Interface: Client가 Server와 통신하는 인터페이스는 아래 4가지 하위 조건을 준수해야 합니다. (REST API와 가장 연관이 깊음)
- Layered System: Client와 Server 사이에 프록시(proxy)·게이트웨이(gateway) 같은 중간 매개 요소를 두고 보안·로드 밸런싱 등을 수행할 수 있어야 합니다. 이를 통해 계층형 층(hierarchical layers)이 형성됩니다.
- Code-On-Demand (Optional): Client는 받아서 바로 실행할 수 있는 applet·script 파일을 Server로부터 받을 수 있어야 합니다. 이 조건은 선택 사항입니다.
Uniform Interface의 4가지 하위 조건
- (4-1) identification of resources: 리소스(resource, 웹상에 존재하는 데이터)를 URI(Uniform Resource Identifier)로 식별할 수 있어야 합니다. URI는 URL의 상위 개념으로, 지금은 URL이라고 생각해도 큰 무리는 없습니다.
- (4-2) manipulation of resources through representations: Client와 Server는 리소스를 직접 다루는 게 아니라 리소스의 ‘표현(representations)‘을 다뤄야 합니다. 예를 들어 ‘오늘 날씨’(
/today/weather) 리소스를 요청했을 때 어떤 Client는 HTML을, 어떤 Client는 PNG를 받을 수 있는데, 이때 HTML·PNG가 리소스의 ‘표현’입니다. 동일 리소스라도 여러 표현이 있을 수 있습니다. - (4-3) self-descriptive messages: Stateless 조건 때문에 Client는 매 리퀘스트마다 필요한 모든 정보를 담아야 하며, 리퀘스트와 리스폰스 모두 그 자체의 정보만으로 모든 것을 해석할 수 있어야 합니다.
- (4-4) hypermedia as the engine of application state: 웹은 ‘분산 하이퍼미디어 시스템’(Distributed Hypermedia System)입니다. 하이퍼미디어(Hypermedia)는 하이퍼텍스트처럼 연결된 ‘문서’에 국한되지 않고 이미지·소리·영상까지 포괄합니다. 이 조건은 Server의 리스폰스에 현재 상태에서 다른 상태로 이전할 수 있는 링크가 포함되어야 한다는 뜻입니다.
오늘날 위 조건들을 모두 제대로 이해하고 준수하기는 쉽지 않습니다. 이 내용은 다소 이론적이므로 일단 넘어가도 괜찮습니다. 기억해야 할 사실은 REST API가 바로 이 REST architecture에 부합하는 API를 의미한다는 점입니다. REST API를 사용하는 웹 서비스를 RESTful 서비스라고 합니다.
개발자들이 강조하는 실천 규칙 2가지
Roy Fielding의 논문에는 구체적·실천적 내용이 제시되어 있지 않지만, 많은 개발자들의 경험으로 형성된 사실상의(de facto) 규칙들이 있습니다. 그중 조건 4(Uniform Interface)의 (4-1)에 관한 2가지를 살펴봅니다.
(1) URL은 리소스를 나타내기 위해서만 사용하고, 리소스에 대한 처리는 메서드로 표현합니다.
즉, URL에서 리소스에 대한 처리를 드러내면 안 됩니다.
- 준수 예시:
https://learn.codeit.kr/api/membersURL + POST 메서드 + 바디에 새 직원 정보 → URL은 리소스만, 처리(추가)는 메서드(POST)로 나타냄. - 위반 예시:
https://learn.codeit.kr/api/members/addURL + GET 메서드 + 바디에 새 직원 정보 → URL에 처리(add)까지 드러냈고, 메서드는 추가가 아닌 조회를 의미하는 GET을 설정함.
(2) 도큐먼트는 단수 명사로, 컬렉션은 복수 명사로 표시합니다.
URL의 path에서는 슬래시(/)로 리소스를 계층적으로 나타냅니다.
https://www.soccer.com/europe/teams/manchester-united/players/pogba
이렇게 하면 URL만으로 무슨 리소스인지 한눈에 알 수 있습니다. 리소스는 특징에 따라 여러 종류로 나뉘는데, 여기서는 **도큐먼트(document)**와 **컬렉션(collection)**을 배웁니다.
- 도큐먼트: 하나의 객체로 표현할 수 있는 리소스 (비유: 하나의 ‘파일’)
- 컬렉션: 여러 도큐먼트를 담을 수 있는 리소스 (비유: 여러 파일을 담는 ‘디렉토리’)
규칙은 도큐먼트는 단수형 명사, 컬렉션은 복수형 명사를 사용한다는 것입니다. 위 URL에서 europe·manchester-united·pogba는 도큐먼트, teams·players는 컬렉션입니다.
이를 앞서 본 학습용 URL과 연결해 보면, members는 member들을 담는 컬렉션이라 복수 명사를 썼고, https://learn.codeit.kr/api/members/3에서는 도큐먼트를 나타내기 위해 단수 명사 대신 직원 고유 식별자 id 값을 썼습니다. (숫자라 단복수 문제가 없습니다.)
도큐먼트·컬렉션 개념을 메서드와 연결해 모든 경우의 수를 정리하면 다음과 같습니다.
| 메서드 | /members | /members/3 |
|---|---|---|
| GET | 전체 직원 정보 조회 | 3번 직원 정보 조회 |
| POST | 새 직원 정보 추가 | X |
| PUT | 전체 직원 정보 수정 (잘 쓰이지 않음) | 3번 직원 정보 갱신 |
| DELETE | 전체 직원 정보 삭제 (잘 쓰이지 않음) | 3번 직원 정보 삭제 |
- 전체 직원을 대상으로 PUT/DELETE를 보내는 것은 전체 수정·삭제를 뜻하므로 위험해서 잘 쓰이지 않으며, Web API 설계에 반영하지 않거나 서버에서 허용하지 않는 것이 일반적입니다.
- POST는 컬렉션(
members)을 대상으로 작업합니다. 컬렉션에 하나의 도큐먼트를 추가하는 것이므로 URL은/members까지만 표현합니다./members/3처럼 특정 도큐먼트에 POST를 보내는 것은 문맥상 맞지 않고, 추가될 직원이 어떤 id를 받을지 알 수도 없습니다.
REST API는 Web API 설계 시 매우 중요하게 고려되는 가이드라인이지만, 6가지 조건을 모두 100% 준수해야 하는지에 관해서는 의견이 많습니다. 그래도 웹 개발자의 단골 면접 주제이니 관심 있다면 더 찾아보세요.
여러 리퀘스트를 연결한 예시
const newMember = {
name: 'Michael',
email: 'michael@codeitmall.kr',
department: 'engineering',
};
fetch('https://learn.codeit.kr/api/members', {
method: 'POST',
body: JSON.stringify(newMember),
})
.then(() => {
fetch('https://learn.codeit.kr/api/members')
.then((response) => response.text())
.then((result) => {
const members = JSON.parse(result);
console.log(members[members.length - 1]);
});
});
Serialization과 Deserialization
- Serialization(직렬화): 자바스크립트 객체 → string 타입의 JSON 데이터 변환
- Deserialization(역직렬화): string 타입의 JSON 데이터 → 자바스크립트 객체 변환
동사로는 ‘Serialize한다’, ‘Deserialize한다’고 표현합니다. 이 두 개념은 자바스크립트뿐 아니라 모든 프로그래밍 언어에서 중요하게 다뤄지는 개념입니다. 어떤 언어든 리퀘스트를 보내거나 리스폰스를 받을 때 이런 작업을 수행해야 하기 때문입니다.
1. string 타입의 JSON 데이터 vs 자바스크립트 객체
- Serialization →
JSON객체의stringify메서드 사용 - Deserialization →
JSON객체의parse메서드 사용
혼동을 막기 위해 데이터 포맷으로서의 json은 소문자로, stringify·parse 메서드를 갖는
JSON객체는 대문자로 씁니다.
각 작업이 왜 필요한지 살펴봅시다.
const obj = { x: 1, y: 2 };
const jsonString = JSON.stringify(obj);
obj라는 객체를 Serialize했습니다. obj와 jsonString을 순서대로 조회해 보면

obj는 자바스크립트 객체이고, Serialize한 결과 jsonString은 string 타입임을 알 수 있습니다. obj 왼쪽 화살표를 클릭해 보면

직접 정의하지 않았지만 기본으로 내장된 프로퍼티들이 존재합니다. 이런 것들은 자바스크립트 실행 환경에서 객체라면 가지는 프로퍼티일 뿐, 서버에는 보낼 필요가 없는 것들입니다. 특히 메서드는 서버가 인식 가능하도록 보낼 방법도 없습니다. 바로 이 때문에 객체가 가진 데이터만을 string 타입으로 변환하는 Serialization이 필요합니다. (지금은 자바스크립트 환경에서만 인식되는 객체를 어느 환경에서든 해석 가능한 포맷으로 변환하기 위해 Serialize한다는 정도로 이해하면 됩니다.)
이번엔 반대로 Deserialize를 해봅니다.
const jsonString = '{"x": 1, "y": 2}';
const obj = JSON.parse(jsonString);
jsonString은 string 타입입니다. 여기서 y 키의 값 2를 가져오려면 어떻게 할까요? 문자열 상태로는 가져올 수 없습니다. 문자열을 직접 파싱(parsing)해 가져올 수도 있지만 그건 원하는 게 아닙니다. 따라서 이 string 값을 Deserialize해서 자바스크립트 객체로 변환해야, obj.y처럼 객체의 프로퍼티에 바로 접근할 수 있습니다.
리스폰스 내용이 JSON일 때 Deserialize하는 이유를 아시겠죠? string 타입에서 어렵게 파싱하기보다, 자바스크립트 객체로 변환해 편하게 다루면 됩니다. 리퀘스트를 보내거나 리스폰스를 받을 때 이 작업을 빼먹지 않도록 주의하세요.
2. text 메서드 말고 json 메서드도 있어요
우리가 배운 코드를 봅시다.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => { const users = JSON.parse(result); });
이 코드는 response.text()로 리스폰스 내용을 추출하고, 그 리턴값인 JSON 데이터(result, 실제로는 JSON 데이터를 품은 Promise 객체)를 JSON.parse(result)로 Deserialize해서 users에 할당했습니다.
여기서 코드 양을 줄이는 방법이 있습니다.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((result) => { const users = result; });
text 메서드 대신 json 메서드를 사용하고, JSON.parse 코드를 삭제했습니다. response 객체의 json 메서드를 호출하면, 리스폰스 내용이 JSON 데이터인 경우 바로 Deserialization까지 수행해 줍니다. 그래서 두 번째 콜백의 result 파라미터로 자바스크립트 객체가 바로 넘어와, JSON.parse를 하지 않아도 됩니다. (단, 내용이 JSON이 아니면 에러가 발생합니다.)
리스폰스 내용이 JSON으로 미리 약속된 경우 text 대신 json 메서드로 Deserialization까지 한 번에 수행하기도 하니 잘 기억해 두세요.
Response와 상태 코드(Status Code)
Response도 head와 body로 이뤄집니다.

그리고 response에서 더 알아야 할 것은 **상태 코드(Status Code)**입니다.


1. 상태 코드에는 대응되는 상태 메시지가 있습니다
모든 상태 코드는 각각 대응되는 상태 메시지(Status Message)를 갖습니다. 예를 들어 200은 OK, 404는 Not Found입니다. 상태 메시지는 코드의 의미를 빠르게 파악하는 데 도움을 줍니다.
2. 상태 코드는 100번대 ~ 500번대까지 있습니다
(1) 100번대 — 정보성 응답(Informational response)
| 코드 | 의미 |
|---|---|
| 100 Continue | 계속 리퀘스트를 보내도 괜찮다고 알려줌. (예: 큰 파일 업로드 전 서버에 미리 물어볼 때) |
| 101 Switching Protocols | 클라이언트의 프로토콜 전환 요청에 ‘그 프로토콜로 전환하겠다’고 응답할 때 |
(2) 200번대 — 성공 처리
| 코드 | 의미 |
|---|---|
| 200 OK | 리퀘스트가 성공적으로 처리됨(포괄적). 메서드에 따라 의미가 다름 — GET=조회 성공, POST=생성 성공, PUT=수정 성공, DELETE=삭제 성공 |
| 201 Created | 리소스가 잘 생성됨. POST 성공 시 200 대신 올 수 있음 |
| 202 Accepted | 리퀘스트가 접수됨. 당장 처리된 건 아니지만 언젠가 처리할 것이라는 뜻 (모아서 한 번에 실행하는 서버 등) |
(3) 300번대 — 클라이언트의 추가 작업 필요
| 코드 | 의미 |
|---|---|
| 301 Moved Permanently | 리소스 위치가 영구히 바뀜. 보통 head에 Location 헤더로 새 URL이 담기며, 브라우저는 자동으로 리다이렉션(redirection) |
| 302 Found | 리소스 위치가 일시적으로 바뀜. Location 헤더에 임시 URL이 담기고 임시 URL로 리다이렉션 |
| 304 Not Modified | 저장된 리소스가 바뀌지 않았으니 재활용하라는 뜻. 리소스를 보내지 않아 네트워크 비용 절약. (캐시 주제와 연관) |
(4) 400번대 — 클라이언트 쪽 문제
| 코드 | 의미 |
|---|---|
| 400 Bad Request | 리퀘스트에 문제가 있음(문법 오류 등) |
| 401 Unauthorized | 아직 신원이 확인되지 않은(unauthenticated) 사용자의 리퀘스트라 처리 불가 |
| 403 Forbidden | 신원은 확인됐지만 해당 리소스 접근 권한이 없음 |
| 404 Not Found | 해당 URL이 나타내는 리소스를 찾을 수 없음 |
| 405 Method Not Allowed | 해당 리소스에 대해 요구한 처리가 허용되지 않음 (예: 조회는 되지만 삭제는 불가) |
| 413 Payload Too Large | 리퀘스트 바디 데이터 용량이 지나치게 커서 서버가 거부 |
| 429 Too Many Requests | 일정 시간 동안 지나치게 많은 리퀘스트를 보냄 |
404 Not Found 예시 — 존재하지 않는 URL(https://www.google.com/abc)에 접속하면 이런 페이지가 보입니다.

(5) 500번대 — 서버 쪽 문제
| 코드 | 의미 |
|---|---|
| 500 Internal Server Error | 알 수 없는 서버 내부 에러로 리퀘스트 처리 불가 |
| 503 Service Unavailable | 서버 점검 중이거나 트래픽 폭주 등으로 서비스 제공 불가 |
상태 코드 또한 ‘Web API 설계’ 시 결정되어야 하는 요소입니다. URL·메서드뿐 아니라 리스폰스의 상태 코드도 상황에 알맞게 설계해야 합니다. 모든 상황을 세분화하는 것은 비효율적일 수 있어, 보통은 꼭 쓸 상태 코드만 추려 특정 유형의 상황을 하나의 코드로 나타내는 전략을 씁니다. 다만 성공·실패에 관계없이 항상 200을 보내는 것은 매우 좋지 않습니다. 가장 이상적인 것은 존재하는 상태 코드를 최대한 많이 활용하는 것입니다.
다양한 데이터 타입
리퀘스트·리스폰스의 body에는 정말 다양한 타입의 데이터가 들어갈 수 있습니다. 지금까지는 주로 JSON을 다뤘지만, 실무에서는 일반 텍스트·이미지·음성·영상 등 수많은 타입을 다루게 됩니다.
1. JSON 말고 XML도 있어요
JSON 외에도 **XML(Extensible Markup Language)**이라는 데이터 포맷이 있습니다. XML은 태그를 사용해서 데이터를 나타내는 것입니다. 예를 들어 이 JSON 데이터를
{
"name":"Michael Kim",
"height":180,
"weight":70,
"hobbies":[
"Basketball",
"Listening to music"
]
}
XML로는 이렇게 나타낼 수 있습니다.
<?xml version="1.0" encoding="UTF-8" ?>
<person>
<name>Michael Kim</name>
<height>180</height>
<weight>70</weight>
<hobbies>
<value>Basketball</value>
<value>Listening to music</value>
</hobbies>
</person>
HTML에서 볼 법한 태그들로 이뤄져 있죠? JSON에서
"name": "Michael Kim"
이라고 나타낸 부분을 XML에서는
<name>Michael Kim</name>
이렇게 시작 태그(<name>)·끝 태그(</name>)와 그 사이의 값으로 나타냅니다.
XML은 JSON이 2013년에 표준화되고 활성화되기 전까지 정말 많이 쓰이던 타입입니다. XML을 쓸 때는 보통 **스키마(Schema)**라는 별도 문서를 함께 사용합니다. 스키마에는 어떤 태그를 쓸 수 있고, 각 태그의 의미는 무엇이며, 특정 태그가 어떤 타입의 값을 가질 수 있는지 등이 담겨 있어, XML은 데이터에 대한 엄격한 유효성(validity) 검증에 특화된 포맷입니다.
하지만 XML은 JSON보다 용량을 더 차지하고, 가독성이 떨어지며, 배우기 어렵다는 문제로 입지가 다소 좁아졌습니다. 특히 자바스크립트 중심의 웹 개발에서는 JSON 문법이 대체로 호환되어 JSON이 편리합니다. 다만 외부 Open API 중에는 여전히 XML로 응답하는 경우가 많으니 처리 방법을 미리 고민해 두는 게 좋습니다. (XML의 Content-Type 헤더 값은 application/xml이며, XML 문법에 특수 규칙을 더한 타입들은 이름 끝에 +xml을 붙입니다.)
2. form 태그에서 사용되는 타입들
JSON·XML 외에 개발자라면 알아둬야 할 데이터 타입이 또 있습니다.
application/x-www-form-urlencoded타입multipart/form-data타입
꼭 알고 가야 할 추가 주제들
웹 개발 기초를 마무리하며, 아직 다루지 못했지만 꼭 알아야 할 3가지 주제를 다룹니다.
1. Ajax
초창기 웹은 링크(공식 명칭 hyperlink)를 클릭하면 새 페이지가 로드되는 방식이었습니다. 하지만 화면 일부분만 바뀌면 되는 경우에도 매번 새 페이지를 로드하는 것은 비효율적이고 사용자 경험에도 좋지 않았습니다. 그래서 2000년대 초부터 Ajax라는 기술이 도입되었습니다.
Ajax는 웹 브라우저가 현재 페이지를 그대로 유지한 채 서버에 리퀘스트를 보내고 리스폰스를 받아, 새 페이지를 로드하지 않고도 변화를 줄 수 있게 해주는 기술입니다. Ajax는 Asynchronous JavaScript And XML의 줄임말로, 자바스크립트를 사용해 비동기적으로(현재 화면에 영향을 주지 않고 백그라운드에서 처리) 통신하는 기술들의 집합입니다. 끝에 XML이 쓰인 것은 용어가 생겨난 당시 XML이 가장 인기 있는 데이터 타입이었기 때문입니다.
Ajax의 원리는 구글 맵(Google Map)을 떠올리면 이해하기 쉽습니다.

구글 맵에서 ‘명동 성당’ 아이콘을 클릭한다고 합시다. 초창기 웹이었다면 새 페이지를 로드해야 했겠지만,

오늘날 구글 맵은 현재 페이지를 그대로 유지한 채 그 밑에 간단한 정보창만 띄웁니다. 브라우저가 현재 페이지를 방해하지 않고 별도로 리퀘스트를 보내 리스폰스를 받아왔기 때문입니다. Ajax 덕분에 부드러운 UX(User Experience, 사용자 경험)를 제공하는 수많은 웹 서비스가 가능해졌습니다.
XMLHttpRequest
자바스크립트에서는 XMLHttpRequest 객체를 통해 Ajax 통신을 할 수 있습니다.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://learn.codeit.kr/api/members');
xhr.onload = function () {
console.log(xhr.response);
};
xhr.onerror = function () {
alert('Error!');
};
xhr.send();
첫 줄에서 XMLHttpRequest 생성자 함수로 객체를 생성한 것이 보이죠? 그런데 예전엔 이렇게 직접 쓸 일이 많았지만 요즘은 그렇지 않습니다.(2020년 1월 기준) 이유는 크게 두 가지입니다.
- 첫째, 이때까지 배운 fetch 함수로 Ajax 통신을 할 수 있기 때문입니다. (fetch가 Ajax 통신 함수였다니 놀랍죠?) fetch는 XMLHttpRequest보다 짧고 간단한 코드로 Ajax 통신을 할 수 있게 해줍니다.
- 둘째, XMLHttpRequest를 기반으로 더 쓰기 편하게 만든 axios라는 패키지가 있기 때문입니다.
실무에서는 fetch 또는 axios를 사용합니다. axios에 더 다양한 기능이 있어 주로 axios를 쓰지만, 외부 패키지를 설치하고 싶지 않으면 fetch를 쓰기도 합니다. fetch의 원리를 알아야 axios도 잘 쓸 수 있어 이번 토픽에서는 fetch를 중심으로 진행합니다.
Ajax 통신 vs 아닌 것
웹 개발 시 Ajax 통신인 것과 아닌 것을 구분할 수 있어야 합니다.
Ajax 통신이 아닌 것 — a 태그 클릭으로 새 페이지를 로드하는 전통적 방식
<a href="https://learn.codeit.kr/api/main">메인 화면으로 가기</a>
Ajax 통신인 것 — 사용자가 느낄 수 없게 리퀘스트·리스폰스를 주고받아 현재 페이지에 변화를 주는 방식
// (위 예시를 단순화한 코드입니다)
function getLocationInfo(latitude, longitude) {
fetch('https://map.google.com/location/info?lat=latitude&lng=longitude')
.then((response) => response.text())
.then((result) => { /* 사용자 화면에 해당 위치 관련 정보 띄워주기 */ });
}
사용자 경험을 고려해
- 언제 새 페이지를 로드하고
- 언제 Ajax 통신으로 현재 페이지 내에서 부드러운 변화를 줄지
잘 결정하는 것이 중요합니다. fetch 함수가 사실은 Ajax 통신을 하는 함수였다는 사실을 꼭 기억합시다.
2. GET, POST, PUT, DELETE 이외의 메서드들
이외에도 알아두면 좋은 2가지 메서드를 소개합니다.
(1) PATCH
PATCH는 기존 데이터를 수정할 때 사용합니다. PUT과의 차이는 다음과 같습니다.
- PUT: 기존 데이터를 아예 새 데이터로 덮어쓰기
- PATCH: 새 데이터로 기존 데이터의 일부만 수정
예를 들어 서버에 이런 데이터가 있을 때,
{
"id": 3,
"name": "Michael",
"age": 25
}
PATCH 메서드로 다음 바디를 보내면,
{
"age": 30
}
age 속성만 갱신됩니다.
{
"id": 3,
"name": "Michael",
"age": 30
}
하지만 같은 바디를 PUT으로 보내면
{
"age": 30
}
이렇게 서버 데이터에는 age 속성만 남게 됩니다. PUT은 덮어쓰기 방식이므로 새 데이터의 온전한 모습 전체를 바디에 담아야 합니다.
{
"id": 3,
"name": "Michael",
"age": 30
}
PATCH는 바디 내용을 기존 데이터의 각 속성과 대조·병합(merge-patch)하면서 수정하므로, 수정할 프로퍼티의 데이터만 넣어도 됩니다.
PUT은 덮어쓰기, PATCH는 일부 수정임을 기억하세요.
(2) HEAD
HEAD 메서드는 GET과 동일하지만, GET 리퀘스트로 받았을 리스폰스에서 body는 제외하고 head 부분만 받는다는 점이 다릅니다. 왜 필요할까요?
예를 들어 용량이 엄청나게 큰 영상 파일을 받기 전에, 용량이 너무 크면 받지 않으려 한다고 합시다. 이때 용량만 조사하기 위해 HEAD 리퀘스트를 보낼 수 있습니다. https://movie.net/matrix(영화 ‘매트릭스’) URL로 HEAD 리퀘스트를 보내면 리스폰스에 body는 없고 head만 옵니다. 대신 Content-length 같은 헤더로 용량 정보를 알 수 있어, 이를 사용자에게 보여주고 시청 여부를 묻는 코드를 작성할 수 있습니다. 실제 데이터가 아니라 데이터에 관한 정보만 얻으려는 상황에 활용됩니다.
각 메서드의 의미를 잘 이해해 두면 Web API 설계에 큰 도움이 됩니다.
3. 웹 통신 말고 다른 통신도 있어요
지금까지 웹에서 이루어지는 통신을 배웠지만, 컴퓨터끼리 통신하는 공간이 웹만 있는 것은 아닙니다. 웹은 ‘HTTP, HTTPS 같은 프로토콜을 사용하여 통신한다’는 특징이 있었죠?
하지만 컴퓨터 통신 프로토콜에는 HTTP·HTTPS 외에도 FTP, SSH, TCP, UDP, IP, Ethernet 등 다양한 종류가 있습니다. 그리고 이런 프로토콜들은 각각 네트워크 통신의 특정 계층에 속합니다.

우리가 배운 HTTP(HyperText Transmission Protocol)는 보통 그 밑에 TCP(Transmission Control Protocol), 그 밑에 IP(Internet Protocol), 그 밑에 Ethernet 프로토콜을 기반으로 동작합니다. 즉, HTTP·HTTPS 통신은 그 하위 프로토콜을 기반으로 이루어집니다. 위로 갈수록 고수준, 아래로 갈수록 저수준 프로토콜이라고 하는데, HTTP는 매우 고수준 프로토콜입니다.
‘웹 개발자’는 당장 HTTP 위에서 이루어지는 일만 공부해도 큰 어려움이 없을 수 있습니다. 하지만 특히 백엔드 개발자는 사용자·리퀘스트 수가 늘수록 HTTP 아래 프로토콜도 어느 정도 알고 있어야 성능 문제 등을 해결할 수 있으니, 미리 관심을 가져두는 게 좋습니다.

또한 이렇게 HTTP 없이 TCP만으로 통신하는 코드를 짜는 개발자들도 많습니다. MMORPG 게임 서버 개발, IOT 기기 개발 등 성능 최적화가 필요한 경우 저수준 프로토콜로 통신하는 프로그램을 개발하기도 합니다. 일반 사용자에게는 웹이 컴퓨터 네트워크의 전부인 것 같지만, 실제로는 웹 밑에 가려진 또 다른 거대한 세계가 있다는 사실을 기억합시다.