[TIL 19] axios를 이용한 HTTP 통신, Cross Origin 문제

nini·2025년 4월 2일

KB IT's Your Life

목록 보기
19/40

axios를 이용한 HTTP 통신

REST API란

REST(Representational State Transfer)

  • 정보를 자원으로 간주 (→ 식별 가능)
  • 각 자원에게 URI를 배정
  • 자원에 대한 조작은 HTTP 메서드로 표현

→ URI와 HTTP 메소드를 이용해 객체화된 서비스에 접근하는 것

CRUD(Create, Read, Update, Delete)에 대응되는 HTTP 메서드

행위HTTP 메서드설명
생성(Create)POST새로운 데이터 추가
조회(Read)GET데이터 조회
수정(Update)PUT 또는 PATCH데이터 수정
삭제(Delete)DELETE데이터 삭제

Query String(?key=value)을 사용하는 경우 (GETDELETE)
Body를 사용하는 경우 (POSTPUTPATCH)

✔ 조회(GET)는 Query String을 사용
✔ 추가/수정(POSTPUTPATCH)는 Body를 사용
✔ 삭제(DELETE)는 보통 Query String을 쓰지만, Body를 사용하는 경우도 있음
✔ 보안이 중요한 데이터는 Body에 포함하여 전송


REST 구성

  • 자원(RESOURCE) - URI
  • 행위(Verb) - HTTP METHOD
  • 표현(Representations) - 일반적으로 JSON 사용
  • REST로 처리되는 URI/METHOD 조합 → REST API
  • REST API 서비스
    • 클라이언트의 종류에 제한을 두지 않음
    • 웹 브라우저, 일반 애플리케이션 등과 통신 가능

(백엔드 서버 없이 테스트)
방법 1. fake server

  • 쉽게 데이터 접근 장점
  • POST, PUT, DELETE 작업 테스트에 어려움

방법 2. json-server

https://www.npmjs.com/package/json-server


json-server

  • 전역 패키지로 설치 npm install–g json-server

  • Mac에서 에러 발생

🛠 권한 오류 해결 방법 (근본적인 해결책)

1️⃣ 전역 패키지 설치 경로를 변경해서 사용자의 홈 디렉터리로 이동하는 것이 가장 안전한 해결책!

mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.zshrc  # Mac 사용자는 ~/.zshrc
source ~/.zshrc

2️⃣ 전역 설치 대신 npx 사용

npx json-server --watch db.json

👉 전역 설치 없이 실행할 수 있음!

3️⃣ 혹시 꼭 필요하면 sudo 사용 (하지만 비추천)

sudo npm install -g json-server

👉 이 방법은 권장되지 않음! (관리자 권한을 자주 요구하게 되고, 파일 소유권 문제를 유발할 수 있음)

<npm 전역 패키지 설치 경로 변경 (추천)>

현재 전역 패키지 설치 경로가 /usr/local/lib/node_modules/인데,
사용자가 접근할 수 있는 홈 디렉터리로 변경하면 권한 문제를 해결할 수 있다

1️⃣ npm 글로벌 디렉터리 생성

mkdir -p ~/.npm-global

2️⃣ npm 전역 패키지 경로 변경

npm config set prefix '~/.npm-global'

3️⃣ 환경 변수에 추가

echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc  # Bash 사용하는 경우

Mac에서 zsh을 사용한다면:

echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.zshrc
source ~/.zshrc

4️⃣ 다시 json-server 설치

npm install -g json-server

5️⃣ 설치 확인

json-server --version

설치가 잘 됐다면 버전 정보가 출력됨



실습

 "todos": [
    {
      "id": "1",
      "todo": "야구장",
      "desc": "프로야구 경기도 봐야합니다.",
      "done": false
    },
    {
      "id": "2",
      "todo": "놀기",
      "desc": "노는 것도 중요합니다.",
      "done": false
    },
    {
      "id": "3",
      "todo": "Vue 학습",
      "desc": "Vue 학습을 해야 합니다",
      "done": false
    },
    {
      "id": "4",
      "todo": "ES6 공부",
      "desc": "ES6공부를 해야 합니다",
      "done": false
    }
  ]

[GET]

[GET] 쿼리문 작성 -> Response 형태 바뀜

[POST] 데이터 추가 -> Body 작성


  • put의 특징 : 수정하려는 것만 보내면 안 된다 → 원본 데이터 전부 다 보내줘야 함
  • id는 수정이 안 됨

axios란?

axios

  • HTTP 기반 통신을 지원하는 자바스크립트 라이브러리
기능axiosfetch
응답 자동 JSON 변환✅ O❌ X (response.json() 필요)
요청 취소✅ O (CancelToken)❌ X
인터셉터 기능✅ O❌ X
타임아웃 설정✅ O (timeout 옵션)❌ X
브라우저 & Node.js 지원✅ O❌ X (브라우저에서만 가능)

구분axiosfetch
모듈 설치설치해야 함 (npm install —save axios)설치할 필요 없음 (브라우저 내장 API)
Promise API사용사용
브라우저 호환성뛰어남IE 지원하지 않음 (IE에서 사용하려면 Polyfill 라이브러리를 사용해야 함)
timeout 기능지원 (timeout 시간 내에 응답이 오지 않으면 중단시킬 수 있음)지원하지 않음
JSON 자동 변환지원 (Content-type 정보를 이용해 자동으로 객체로 변환함)지원하지 않음 (수신한 JSON 데이터를 객체로 변환하는 Promise 체인을 추가해야 함)

proxy server

vite.config.js 변경(json-server를 프록시 서버로 설정)

// 이 코드 아래에 추가하기
// 개발 서버(개발시에만 동작을 함)
server: {
	proxy: {
		'/api': {
			target: 'http://localhost:3000',
			changeOrigin: true,
			rewrite: (path) => path.replace(/^\/api/, ''),
		},
	},
},

App.vue(App.vue를 axios를 이용해 /api/todos/1를 요청하고, 그 결과를 콘솔에 출력)

// 절대 경로 → 어디에서 실행하든 항상 같은 서버로 요청
const url = 'http://localhost:3000/todos/1'; 

// 상대 경로 → 현재 실행 중인 프론트엔드 서버를 기준으로 요청
const url = '/api/todos/1';

두 코드의 차이점!

const url = '<http://localhost:3000/todos/1>';
const url = '/api/todos/1';

👉 두 코드의 의미는 다름!


1️⃣ 첫 번째 코드 (http://localhost:3000/todos/1)

const url = '<http://localhost:3000/todos/1>';
axios.get(url);
  • 절대 경로 (Absolute URL)
  • 요청이 항상 http://localhost:3000/todos/1로 직접 전송됨
  • 클라이언트가 어디에서 실행되든 항상 같은 URL로 요청함

🔹 예제 (프론트엔드가 http://localhost:5173에서 실행 중일 때)

🚀 요청이 그대로 http://localhost:3000/todos/1로 전송됨

✔ 백엔드 서버가 localhost:3000 에 존재해야 함


2️⃣ 두 번째 코드 (/api/todos/1)

const url = '/api/todos/1';
axios.get(url);
  • 상대 경로 (Relative URL)
  • 요청하는 도메인(출처, origin)을 기준으로 상대적으로 URL을 해석함.
  • 현재 실행 중인 프론트엔드 서버의 출처(origin)에 따라 달라짐.

🔹 예제 (프론트엔드가 http://localhost:5173에서 실행 중일 때)

🚀 요청이 http://localhost:5173/api/todos/1 로 전송됨!

💡 이 경우 백엔드가 localhost:3000이면, CORS 오류가 발생할 수 있음!

✔ 해결 방법 → 프록시 설정 (package.jsonproxy 추가)

"proxy": "<http://localhost:3000>"

👉 그러면 axios.get('/api/todos/1') 하면 자동으로 http://localhost:3000/api/todos/1로 요청이 전송됨! 🎯


정리

코드설명요청 결과
'<http://localhost:3000/todos/1'>절대 경로 → 어디에서 실행하든 항상 같은 서버로 요청http://localhost:3000/todos/1
'/api/todos/1'상대 경로 → 현재 실행 중인 프론트엔드 서버를 기준으로 요청예: http://localhost:5173/api/todos/1

👉 즉, 실행 환경에 따라 백엔드 서버 주소가 다를 경우, 상대 경로를 사용하고 프록시를 설정하는 것이 좋음!


axios 라이브러리 사용법

Promise와 async-await

  • axios의 비동기 처리
    • 함수의 마지막 인자에 콜백 함수가 있는 경우
      • 작업 완료시 콜백 함수 호출
    • 콜백 함수 인자가 없는 경우
      • promise 객체를 리턴
<template>
  <div><h2>콘솔을 확인합니다.</h2></div>
</template>

<!-- async/await axios를 이용해서 todo 목록을 얻어 출력 -->
<script setup>
import axios from 'axios';

const listUrl = '/api/todos';
const todoUrlPrefix = '/api/todos/';

// 전체 목록을 조회한 후 한 건씩 순차적으로 순회하며 조회하기
const requestAPI = async () => {
  let todoList;
  let response = await axios.get(listUrl);

  todoList = response.data;
  console.log('# TodoList : ', todoList);

  for (let i = 0; i < todoList.length; i++) {
    response = await axios.get(todoUrlPrefix + todoList[i].id);
    console.log(`# ${i + 1}번째 Todo : `, response.data);
  }
};
requestAPI();
</script>
✅ async/await 개념

async (비동기 함수 선언)
- 함수 앞에 async를 붙이면 비동기 함수가 됨
- async 함수 내부에서는 await을 사용할 수 있음

await (비동기 처리 대기)
- await은 Promise(비동기 작업)가 완료될 때까지 기다림
- await axios.get(url) → 요청이 끝날 때까지 코드 실행을 멈춤

✅ 코드 실행 흐름

1️⃣ 전체 TODO 목록을 가져오기
- await axios.get(listUrl)을 호출해 전체 목록을 가져옴
- 응답을 todoList에 저장하고, 콘솔에 출력

2️⃣ 반복문을 돌며 개별 TODO 조회
- for 루프를 이용해 todoList의 각 항목을 순차적으로 조회
- await axios.get(todoUrlPrefix + todoList[i].id)로 하나씩 가져옴
- 요청이 완료될 때까지 기다렸다가 콘솔에 출력

axios.get() 메서드

  • GET 요청 처리
  • 사용방법
    • url: 요청하는 백엔드 API의 URL을 지정
    • config: 요청시에 지정할 설정값들
    • 요청 후에는 Promise를 리턴하며 처리가 완료된 후에는 response 객체를 응답받음 axios.get(url, config)

GET 요청: Query String은 자동으로 URL 인코딩됨
POST 요청: application/json일 경우 자동 인코딩되지 않음. 직접 인코딩 필요!

  • axios.get(url): /api/todos로 요청을 보내고 응답을 받음
  • await을 사용하여 응답이 올 때까지 기다림
  • response객체를 콘솔에 출력하여 데이터 확인
<template>
  <div><h2>콘솔을 확인합니다.</h2></div>
</template>

<script setup>
import axios from 'axios'; // Axios 라이브러리 불러오기

const requestAPI = async () => {
  const url = '/api/todos'; // 요청을 보낼 API 주소
  const response = await axios.get(url); // 서버에 GET 요청을 보내고 응답을 기다림
  console.log('# 응답객체 : ', response); // 응답 객체 전체를 출력
};

requestAPI(); // 함수 실행
</script>

axios.response 객체

속성설명예시
data수신된 응답 데이터(서버에서 반환한 실제 응답 데이터){ "id": 1, "title": "Example" }
config요청시에 사용된 config 옵션(설정 값){ method: "get", url: "https://..." }
headers백엔드 API 서버가 응답할 때 사용된 응답 HTTP 헤더{ "content-type": "application/json" }
request요청 객체 (브라우저 XHR, Node.js에서는 http.ClientRequest){}
status서버가 응답한 HTTP 상태 코드200404500 등
statusText서버의 HTTP 상태를 나타내는 문자열 정보(HTTP 상태 메시지)"OK""Not Found" 등

axios.post() 메서드

  • POST 요청 처리
    • 데이터를 서버로 전송하여 서버에서 새로운 데이터 항목 생성(create)
      • url, config는 axios.get()과 동일
      • data는 POST 요청의 HTTP Content Body로 전송할 데이터 axios.post(url, data, config)
<template>
  <div><h2>콘솔을 확인합니다.</h2></div>
</template>

<script setup>
import axios from 'axios';

const requestAPI = async () => {
  const url = '/api/todos';
  let data = { todo: '윗몸일으키기 3세트', desc: '너무 빠르지 않게...' };
  const resp1 = await axios.post(url, data);
  console.log(resp1.data);
};
requestAPI();
</script>

기타 axios 함수

  • axios.get(url, config)

  • axios.post(url, data, config)

  • axios.put(url, data, config)

  • axios.delete(url, config)


axios 기본 설정 변경

  • config 값을 전달하지 않으면 기본값이 사용됨

  • 기본값의 변경

    • axios.defaults.baseURL = ‘/api/todos’;
    • axios.defaults.headers.common[’Authorization’] = JWT;
    • axios.defaults.timeout = 2000;

에러 처리

try~catch

// 개발자용
try {
	const response = await axios.get(url, { timeout: 900 });
	console.log("# 응답객체 : ", response);
} catch (e) {
	console.log("## 다음 오류가 발생했습니다.");
	if (e instanceof Error) console.log(e.message);
	else console.log(e);
}
  • async/await 사용
<script setup>
import axios from 'axios';

const requestAPI = async () => {
  const url = '/api/todos';
  try {
    const response = await axios.get(url, { timeout: 900 }); // ⏳ 응답을 기다림
    console.log('# 응답객체 : ', response);
  } catch (e) {
    console.log('## 다음 오류가 발생했습니다.');
    if (e instanceof Error) console.log(e.message);
    else console.log(e);
  }
};
requestAPI();
</script>
  • then/catch 사용
<script setup>
import axios from 'axios';

const requestAPI = async () => {
  const url = '/api/todos2';
  axios
    .get(url, { timeout: 900 }) // ⏳ 비동기 요청 (응답을 기다리지 않음)
    .then((response) => {
      console.log('# 응답객체 : ', response);
    })
    .catch((e) => {
      console.log('에러=================');
      console.log(e);
      if (e instanceof Error) console.log(e.message);
      else console.log(e);
    });
};
requestAPI();
</script>

async/await vs then/catch 차이점 비교 표

구분async/await 방식then/catch 방식
코드 스타일동기 코드처럼 읽기 쉬움콜백 체이닝 방식
응답 처리 방식await으로 응답을 기다린 후 변수에 저장비동기적으로 진행, then()에서 응답 처리
에러 처리 방식try...catch로 예외 처리catch()에서 예외 처리
실행 흐름요청이 끝날 때까지 다음 코드 실행 안 됨요청이 끝나지 않아도 다음 코드가 실행됨
가독성✅ 좋음 (더 직관적)❌ 콜백 체이닝이 길어지면 가독성 저하 가능
추천 상황- 순차적인 요청이 필요한 경우 (ex: API 응답을 받고 추가 요청)
- 가독성이 중요한 경우
- 여러 개의 비동기 호출이 있는 경우
- 간단한 요청일 때
- 병렬적으로 여러 요청을 처리해야 할 때 (Promise.all()과 함께 사용)
예제 코드js try { const response = await axios.get(url); console.log(response); } catch (e) { console.error(e.message); }js axios.get(url) .then(response => console.log(response)) .catch(e => console.error(e.message));

결론:

순차적 요청이 필요하고 가독성이 중요한 경우 → async/await

단순한 비동기 요청이거나 여러 요청을 병렬 처리할 경우 → then/catch


Cross Origin 문제

👩‍💻 "내가 만든 웹사이트에서 다른 서버에 데이터를 요청했는데, 오류가 뜬다?" 🤯

👉 그게 바로 크로스 오리진(CORS) 문제


🔍 CORS가 발생하는 이유?

웹 브라우저는 보안 정책 때문에,

🚫 다른 출처(origin)의 서버에서 데이터를 가져오지 못하게 막음

✅ 출처(origin)란?

"출처(origin)" = 프로토콜 + 도메인 + 포트번호

  • http://example.com (출처 A)
  • https://example.com (출처 B, 프로토콜 다름)
  • http://api.example.com (출처 C, 도메인 다름)
  • http://example.com:3000 (출처 D, 포트 다름)

💡 출처가 다르면?

👉 브라우저가 보안 정책(Same-Origin Policy) 때문에 요청을 차단함!

크로스 오리진 문제 해결 방법

크로스 오리진(Cross Origin) 문제

“브라우저는 자신의 오리진과 다른 오리진의 API 서버와 통신할 때 문제가 발생한다”는 개념

  • 오리진(Origin)
    • 현재 요청 페이지를 받은 서버의 정보(주소와 포트번호)
  • 브라우저의 기본 보안 정책
    • 동일 근원 정책: SOP(Same Origin Policy)
    • 브라우저의 오리진과 동일한 오리진을 가진 서버일 때만 통신 가능
    • 다른 오리진의 서버와 통신을 허용하지 않음

해결 방법

  • 백엔드 API 서버측에서 CORS(Cross Origin Resource Sharing)라는 기능을 제공

    • Origin이 달라도 통신을 허용해 줌
    • 서버가 "이 웹사이트에서 요청해도 괜찮아!"라고 허락하면 해결
      • 서버가 응답할 때 Access-Control-Allow-Origin HTTP 헤더를 추가하면 됨!
  • 프론트엔드 애플리케이션을 호스팅하는 웹서버에 프록시(Proxy)를 설치 또는 설정

    • 개발 단계에서 주로 사용

      프록시 서버 사용(브라우저가 직접 요청 못 하게 우회)
      웹 브라우저 대신, 내 서버가 요청을 보내게 하면 됨
      → CORS 문제는 브라우저 보안 정책 때문에 발생하는 거라, 브라우저가 아닌 서버 간 통신에서는 발생하지 않음
      → 프록시 서버를 만들어 백엔드 서버와 직접 통신하면 해결


프록시를 이용한 우회

  • 프론트엔드 앱의 입장에서는 동일한 Origin으로 통신
  • 개발용 웹 서버가 대신 통신을 중개해 줌
profile
사용자를 고려한 디자인과 UX에 관심있는 개발자

0개의 댓글