[Vue] Axios/CORS(CRUD 연동)

배창민·2025년 11월 28일
post-thumbnail

Axios/CORS(CRUD 연동) 정리


1. Axios

1-1. Axios가 필요한 이유

브라우저 내장 fetch 만으로도 통신은 가능하지만, 실제 서비스에서는 Axios를 거의 기본처럼 쓴다.

이유:

  • 에러 처리 구조가 더 명확하다 (try/catch 로 한 번에 처리)
  • 응답을 기본적으로 JSON 으로 파싱해 준다
  • 요청·응답 인터셉터로 공통 로직을 한 곳에 모을 수 있다
  • baseURL, 공통 header, 토큰 설정을 인스턴스 단위로 관리할 수 있다
  • 파일 업로드, 인증 토큰 처리 등에서 편하다

결론: Vue + 실제 API 서버 연동에서는 Axios 인스턴스를 만드는 흐름이 거의 정석이다.


1-2. Axios 인스턴스 개념

그냥 이렇게 매번 써도 되지만:

import axios from 'axios';

axios.get('/products');
axios.post('/login', payload);

실무에서는 보통 인스턴스를 한 번 만들고 그걸 재사용한다.

// src/api/axios.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:8080/api',  // 공통 prefix
  withCredentials: true                  // 필요 시
});

// 요청 인터셉터
api.interceptors.request.use(config => {
  // 토큰 자동 첨부 등
  // config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// 응답 인터셉터
api.interceptors.response.use(
  res => res,
  error => {
    // 공통 에러 처리
    return Promise.reject(error);
  }
);

export default api;

장점:

  • api.get('/products') 처럼 상대 경로만 써도 됨
  • 공통 헤더, 토큰, 에러 처리 로직을 한 군데에 모을 수 있음

1-3. 주요 HTTP 메서드

메서드역할
GET데이터 조회
POST새 데이터 생성
PUT전체 수정
PATCH부분 수정
DELETE삭제

Axios 호출 예:

api.get('/products');             // GET /products
api.post('/products', data);      // POST /products
api.put(`/products/${id}`, data); // PUT /products/{id}
api.delete(`/products/${id}`);    // DELETE /products/{id}

1-4. async/await 패턴

Axios 는 Promise 기반이라 async/await 형태로 사용하는 게 보통 더 깔끔하다.

try {
  const res = await api.get('/products');
  const products = res.data;
} catch (error) {
  // error.response, error.message 등 활용
}

한 번 더 정리:

  • res.data 에 실제 응답 바디가 들어간다
  • 에러는 catch 에서 한 번에 처리

2. CORS(Cross-Origin Resource Sharing)

Vue 개발 서버와 백엔드 서버가 포트가 다르면 거의 무조건 CORS 문제가 발생한다.

예:

  • 프론트: http://localhost:5173
  • 백엔드: http://localhost:8080

브라우저 기준으로 출처(origin) 이 다르기 때문에, 브라우저가 기본적으로 요청을 막는다.
이 규칙이 동일 출처 정책(Same-Origin Policy).


2-1. CORS 동작 방식

브라우저는 특정 조건에서 Preflight(사전 요청) 을 먼저 보낸다.

  • 메서드가 GET/POST 이외이거나
  • 커스텀 헤더를 포함하거나
  • JSON 이 아닌 다른 타입을 보내는 경우 등

이때 보내는 요청이 OPTIONS 이고, 여기서 묻는 내용은 단순하다.

이 출처(origin)에서 이 메서드/헤더로 요청 보내도 되나?

서버가 CORS 관련 헤더로 “허용” 응답을 보내야 실제 요청이 이어진다.


2-2. 백엔드(Spring Boot)에서 해결

결론부터 말하면: CORS는 서버가 허용해 줘야 해결된다.

Spring 쪽에서 주로 쓰는 방법:

  1. 컨트롤러에 @CrossOrigin 붙이기
  2. WebMvcConfigurer 로 글로벌 CORS 설정
  3. Spring Security 의 SecurityFilterChain 에 CORS 설정 추가

핵심은 이런 헤더들을 백엔드에서 설정하는 것:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials (쿠키/인증 정보 쓸 때)

2-3. 프런트(Vite devServer proxy)에서 우회

개발 환경에서만 쓰는 편법도 있다.

vite.config.js 에 proxy 를 설정하면, 브라우저 입장에서는 같은 origin으로 요청하는 것처럼 보이게 만들 수 있다.

예를 들어:

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',  // 백엔드 주소
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
});

이렇게 하면 프런트에서는:

api.get('/api/products');

라고 요청하고, Vite dev 서버가 내부적으로:

http://localhost:8080/products

로 프록시를 대신 날려 준다.

브라우저 입장에서는 http://localhost:5173/api/... 로 보이기 때문에 CORS 검사에 걸리지 않는다.


3. CRUD 흐름 – Product 예시

프런트에서 API 서버와 상호작용할 때 가장 기본적인 흐름을 Product 예시로 정리.


3-1. 목록 페이지 (GET)

역할:

  • /products 호출
  • 배열 응답을 받아 화면에 리스트 렌더링
  • 로딩 중 / 에러 상태를 같이 관리

핵심 포인트:

  • Axios GET + async/await
  • 로딩 상태 관리 (isLoading 같은 ref)
  • 에러 메시지 표시용 상태 (errorMessage 등)

3-2. 상세 페이지 (GET + Route Params)

URL 패턴:

/products/:id

흐름:

  1. Vue Router 의 useRoute()id 가져오기
  2. Axios 로 /products/{id} 호출
  3. 성공 → 상세 정보 렌더링
  4. 404 → “상품을 찾을 수 없음” UI

핵심 포인트:

  • 동적 라우트 params 사용
  • API 결과에 따라 분기 처리 (null/404 등)

3-3. 등록 페이지 (POST)

흐름:

  1. ref / reactive 로 form 상태 정의
  2. <input v-model="form.name"> 형태로 바인딩
  3. onSubmit() 에서 Axios POST 호출
  4. 성공 → 목록 페이지로 이동 (router.push('/products'))
  5. 실패 → 에러 메시지 출력

포인트:

  • form 상태와 API 요청 payload 를 어떻게 매핑할지
  • validation (비어 있으면 막기 등)
  • 서버에서 내려오는 에러 메시지를 그대로 보여 줄지, 가공할지 결정

3-4. 수정 페이지 (PUT)

흐름:

  1. 처음 들어왔을 때 /products/{id} GET 으로 기존 데이터 조회
  2. 조회된 데이터를 form 초기값으로 세팅
  3. 유저가 수정 → 제출 시 /products/{id} 로 PUT 요청
  4. 성공 → 상세 페이지나 목록으로 이동

포인트:

  • “등록”과 “수정”에서 form 컴포넌트를 공통으로 쓰는 패턴 자주 사용
  • GET 실패, PUT 실패 각각의 에러처리를 나누는 게 좋다

3-5. 삭제 (DELETE)

흐름:

  1. 상세 페이지 혹은 목록에서 “삭제” 버튼 클릭
  2. confirm() 으로 한 번 더 확인
  3. Axios DELETE /products/{id}
  4. 성공 → 목록 페이지로 router.replace('/products') 같은 형태로 이동

포인트:

  • 삭제 후 뒤로 가기를 눌러도 다시 상세 페이지로 못 돌아오게 하고 싶으면 replace 사용
  • 삭제 실패(권한 없음, 서버 오류 등) 메시지 처리

3-6. API 에러 응답 매핑

백엔드에서 이런 식으로 응답을 내려준다고 가정하면:

{
  "code": "NOT_FOUND",
  "message": "상품이 존재하지 않습니다.",
  "status": 404
}

프런트에서는 보통:

  • error.response.data 를 읽어서

    • data.message → 화면에 보여 줄 문구
    • data.status → 분기 처리
  • 공통 규칙 예:

    • 401 → 로그인 페이지로 이동
    • 403 → “접근 권한 없음” 메시지
    • 404 → “존재하지 않는 리소스” 페이지
    • 500 → “서버 오류 안내” 토스트/알림

이걸 Axios 인스턴스의 응답 인터셉터에서 공통 처리하는 패턴도 자주 쓴다.


4. 프런트 구조화 – 라우터, 폼, 에러 처리

4-1. 라우터 설계

Product CRUD 구조 예:

/products          # 목록
/products/:id      # 상세
/products/new      # 등록
/products/:id/edit # 수정

각 URL 마다 별도 페이지 컴포넌트를 두고, 라우터가 흐름을 관리한다.


4-2. 목록 → 상세 이동

목록에서 아이템 클릭 → 상세 페이지 이동:

<RouterLink :to="`/products/${item.id}`">
  {{ item.name }}
</RouterLink>

혹은 코드에서:

router.push(`/products/${item.id}`);

4-3. Form 데이터 처리 기본 패턴

  1. reactive 또는 ref 로 form 상태 선언
  2. v-model 로 input 과 연결
  3. submit 시 Axios 요청 호출

예:

<script setup>
import { reactive } from 'vue';
import api from '@/api/axios';
import { useRouter } from 'vue-router';

const router = useRouter();

const form = reactive({
  name: '',
  price: 0
});

const onSubmit = async () => {
  try {
    await api.post('/products', form);
    router.push('/products');
  } catch (error) {
    // 에러 처리
  }
};
</script>

4-4. Axios 에러 처리 UI 패턴

보통 세 가지 상태를 같이 관리한다.

  1. isLoading

    • 요청 중이면 버튼 비활성화, 스피너 표시
  2. errorMessage

    • 실패했을 때 화면에 보여 줄 메시지
  3. success 혹은 페이지 이동

    • 성공 시 목록 이동, 토스트 표시 등

간단한 구성 예:

const isLoading = ref(false);
const errorMessage = ref('');

const onSubmit = async () => {
  isLoading.value = true;
  errorMessage.value = '';

  try {
    await api.post('/products', form);
    router.push('/products');
  } catch (error) {
    errorMessage.value = error.response?.data?.message || '요청 실패';
  } finally {
    isLoading.value = false;
  }
};

정리

  • Axios 인스턴스로 API 공통 설정 + 인터셉터를 관리

  • CORS 는 서버 설정이 핵심, 개발 단계에서는 Vite proxy 로 우회 가능

  • Product CRUD 흐름을 기준으로

    • GET 목록 / 상세
    • POST 등록
    • PUT 수정
    • DELETE 삭제
  • 라우터로 페이지 구조를 잡고

    • /products, /products/:id, /products/new, /products/:id/edit
  • Form 상태, 로딩 상태, 에러 상태를 반응형으로 관리해서

    • UX 좋은 API 연동 화면을 만든다
profile
개발자 희망자

0개의 댓글