HTTP란?
- OSI 7계층 중 Application 계층은 7계층에서 사용하는 프로토콜 (요청/응답에 대한 정해진 양식)이다.
- 우리는 application 개발자가 될 생각이니 가장 외부 계층 프로토콜인 HTTP를 가장 많이 사용한다.
(그 외에도 4계층 Transport TCP와 3계층 Network IP도 알고있긴 해야함)


우리가 브라우저 주소창에 www.example.com을 입력하고 엔터만 치면,
브라우저가 이 주문서를 자동으로 작성해서 서버에 보냅니다.
우리가 직접 GET /index.html HTTP/1.1이라고 타이핑하지 않아도요.
HTTP 요청: 주문서 작성하기
HTTP 응답: 영수증 + 음식 받기
HTTP 상태코드 (응답 종류)
- 직원의 대답 유형

HTTP 메서드 (요청 종류)
CRUD (crate, read, update, delete)와 관련있다
- 주문의 종류

HTTP Header (요청/응답 추가 정보)
- 주문서의 추가 정보


REST API (응답 설게 규칙)
- 메뉴판 설계 규칙



JSON이란?
- Javascript object notation
- 데이터 주고받을때 사용하는 테스트 형식 (JS object 형식처럼 보임)
{
"name": "홍길동", // 키는 큰따옴표로
"age": 30, // 숫자
"married": false, // 불린
"children": null, // null
"hobbies": ["독서", "운동"] // 배열
}
- JSON.stringify(): 객체를 JSON 문자열로 (왜냐하면 HTTP 프로토콜을 문자열만 통과할 수 있음)
- JSON.parse(): JSON 문자열을 객체
const user = {
name: '홍길동',
age: 30,
email: 'hong@example.com'
};
// 객체 → JSON 문자열
const json = JSON.stringify(user);
console.log(json);// '{"name":"홍길동","age":30,"email":"hong@example.com"}'
console.log(typeof json); // "string"
const json = '{"name":"홍길동","age":30}';
// JSON 문자열 → 객체
const user = JSON.parse(json);
console.log(user); // { name: '홍길동', age: 30 }
console.log(user.name); // "홍길동"
console.log(typeof user); // "object"
(+) Date객체, 함수, undefined인 경우에만 조금 형식이 달라진다.
// Date
const data = { createdAt: new Date() };
const parsed = JSON.parse(JSON.stringify(data));
console.log(typeof parsed.createdAt); // "string" ← Date가 아님!
// 함수
const data = { greet: () => "안녕" };
const parsed = JSON.parse(JSON.stringify(data));
console.log(parsed.greet); // undefined ← 사라짐!
// undefined
const data = { name: "홍길동", age: undefined };
const parsed = JSON.parse(JSON.stringify(data));
console.log(parsed.age); // undefined
Fetch 기본 문법
- fetch는 JS에서 서버에 HTTP 요청을 보내는 함수이다 .
- 브라우저/NodeJS에 내장되어 있는 함수이다.
- Promise를 반환한다. (async/await로 처리)
GET은 기본 default이다.
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => {
console.log('응답 객체:', response);
return response.json(); // JSON 파싱
})
.then(data => {
console.log('데이터:', data);
});
async function getUser() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const data = await response.json();
console.log('사용자:', data);
}
getUser();

데이터를 한꺼번에 보내는게 아니라 쪼개서 보내고 나중에 합쳐진다.
- response.ok가 true면 200번대이고, 아니면 400~500번대이다.
- response.json()은 body를 읽고 JSON으로 변환하는 비동기 작업 -> Promise 리턴
- response.status는 HTTP 상태 코드 숫자가 나온다 (200/201/500...)
- new URLSearchParams(param)하면 일반 객체로부터 url의 쿼리파라미터를 부분을 작성해준다
async function searchUsers(params) {
const searchParams = new URLSearchParams(params);
const url = `https://api.example.com/users?${searchParams}`;
const response = await fetch(url);
const data = await response.json();
return data;
}
// 사용
searchUsers({
name: '홍길동',
age: 30,
city: '서울'
});
// → https://api.example.com/users?name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=30&city=%EC%84%9C%EC%9A%B8
나머지 POST, PATCH, PUT, DELETE는 다음과 같이 요청한다.
PATCH는 수정, PUT은 전체 데이터 교체이다.
PUT으로 하는거 PATCH로 할 수 있으니 대게 그냥 PATCH만 쓴다
DELETE는 보통 body를 안 주고 그냥 상태코드만 주기 때문에 json파싱을 안한다 사용하는 프로토콜 (요청/응답에 대한 정해진 양식)이다.
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // HTTP 메서드
headers: {
'Content-Type': 'application/json' // JSON 형식
},
body: JSON.stringify(newPost) // 객체를 JSON 문자열로
});
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedPost)
});
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'DELETE'
});
Fetch/Axios 함수의 리팩토링

API fetch 함수를 작성하다 보면 코드가 반복되고 있다는 느낌이 들기 시작할 것이다.
DRY: don't repeat yourself
-> 두번 이상 반복하면 리팩토링 해야한다는 소리다!
그래서 위의 코드의 반복되는 부분을 또다른 함수를 정의해서 해결해보자
- endpoint: url에서 BASE_URL을 제외한 끝지
const BASE_URL = 'https://jsonplaceholder.typicode.com';
async function get(endpoint) {
const response = await fetch(`${BASE_URL}${endpoint}`);
if (!response.ok) {
throw new Error(`HTTP 에러! 상태: ${response.status}`);
}
return response.json();
}
const posts = await get('/posts');
const user = await get('/users/1');
const comments = await get('/comments');
이러면 코드 줄이 훨씬 줄어들고 보기에도 편하다.
GET외의 다른 함수들도 리팩토링하면 다음과 같게 생긴다:
const BASE_URL = 'https://jsonplaceholder.typicode.com';
const api = {
get: async (endpoint) => {
const response = await fetch(`${BASE_URL}${endpoint}`);
if (!response.ok) throw new Error(`GET 실패: ${response.status}`);
return response.json();
},
post: async (endpoint, data) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`POST 실패: ${response.status}`);
return response.json();
},
put: async (endpoint, data) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`PUT 실패: ${response.status}`);
return response.json();
},
delete: async (endpoint) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'DELETE',
});
if (!response.ok) throw new Error(`DELETE 실패: ${response.status}`);
if (response.status === 204) return null;
return response.json();
},
};
근데 이거마저 코드가 반복되고 있는 것을 확인할 수 있다.
fetch -> ok 체크 -> json 파싱 흐름이 계속 똑같다.
const BASE_URL = 'https://jsonplaceholder.typicode.com';
async function request(endpoint, options = {}) {
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
};
const response = await fetch(`${BASE_URL}${endpoint}`, config);
if (!response.ok) {
throw new Error(`HTTP 에러! 상태: ${response.status}`);
}
// DELETE는 응답 본문이 없는 경우가 많음
if (response.status === 204) return null;
return response.json();
}
const api = {
get: (endpoint) => request(endpoint),
post: (endpoint, data) => request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
}),
put: (endpoint, data) => request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
}),
delete: (endpoint) => request(endpoint, {
method: 'DELETE',
}),
};
Fetch 오류 처리
try-catch로 에러를 잡으려 해도,
404같은, 백엔드에 요청은 갔지만 HTTP 에러가 난 경우는 fetch가 에러를 내지 않기 때문에
잡기 쉽지 않다.
그래서 response.ok를 가지고 HTTP에러가 나면 직접 throw new Error해서
try-catch가 잡을 수 있게 만들어줘야한다.


async function fetchData(url) {
try {
const response = await fetch(url);
// HTTP 에러 체크 (404, 500 등)
if (!response.ok) {
throw new Error(`HTTP 에러! 상태: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// 네트워크 에러 + 위에서 던진 HTTP 에러 모두 여기서 잡힘
console.error('요청 실패:', error.message);
throw error;
}
}
상태코드별 처리
에러가 났을때 상태코드에 따라 다른 메세지를 보여주는게 이상적이다.
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
const messages = {
400: '잘못된 요청입니다',
401: '로그인이 필요합니다',
403: '접근 권한이 없습니다',
404: '요청한 데이터를 찾을 수 없습니다',
500: '서버에 문제가 발생했습니다',
};
throw new Error(messages[response.status] || `에러: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(error.message);
throw error;
}
}
타임아웃 - 응답이 너무 느릴때
응답 기다리는 데에 시간 제한을 걸 수 있다.
fetch의 signal 옵션에AbortSignal.timeout(밀리초)를 넣으면 된다.
// 3초 안에 응답이 안 오면 자동으로 취소
const response = await fetch('/api/data', {
signal: AbortSignal.timeout(3000),
});
재시도 - 실패하면 다시 시도하기
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error(`시도 ${i + 1}/${retries} 실패`);
// 마지막 시도였으면 포기
if (i === retries - 1) throw error;
// 1초 기다렸다가 재시도
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
정리

'코드잇 스터디' 카테고리의 다른 글
| [React] React로 데이터 다루기 (1) | 2026.04.22 |
|---|---|
| [React] 리엑트에 대하여 (0) | 2026.04.21 |
| [JS 기초문법] 인터랙티브 JS (0) | 2026.04.09 |
| [JS기초문법] JS 복습해야할 헷갈리는 문법 (0) | 2026.04.06 |
| [JS기초문법] for...of, for...in (0) | 2026.04.06 |