React api 연동 (axios + http-proxy-middleware)

하람·2024년 7월 23일

항상 반복하는 작업.. 정리해놓겠다.

http-proxy-middleware 설정

npm i -S http-proxy-middleware

/src/setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:8000',
      changeOrigin: true,
      pathRewrite: {
        '^/api': '', // URL ^/server -> 공백 변경
      },
    })
  );
};
  • 서버와 통신하는 url을 작성할 땐 /api를 엔드포인트로 놓는다. 알아서 setupProxy.js에서 설정한 서버 url로 변경된다.
  • 추후 개발/프로덕션 엔드포인트를 구별해야할 때 유용하다.

Axios 설정

npm i -S axios

1. /api/config.js

import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';

export default axios;
  • axios에 기본적으로 csrftoken과 X-CSRFToken 설정을 해놓는다. 앞으로 axios를 불러올 땐 config.js를 통해 import한다.

2. /api/api.js

서버와 통신하는 (/api) 용도의 api들을 호출할 때 사용할 axios 객체를 생성한다.

import moment from 'moment';
import axios from './config';
import { refresh, refreshErrorHandle } from './refresh.ts';
import auth from './accountAPI';

const Api = axios.create({
  timeout: 10000,
  params: {},
});

Api.interceptors.request.use(refresh, refreshErrorHandle); // 요청 보내기 전 토큰 유효성 검사
Api.interceptors.response.use(
  // 응답이 401에러인경우 refresh 후 다시 요청 수행해보기
  (res) => res,
  async (err) => {
    const {
      config,
      response: { status },
    } = err;

    /**  refresh 요청자체의 에러나 401 에러가 아닌경우는 refresh를 해야할 필요가 없으므로 에러를 그대로 reject */
    if (String(config.url).includes('/accounts/dj-rest-auth/token/refresh/') || status !== 401 || config.sent) {
      return Promise.reject(err);
    }

    /** refresh 요청이 끝나고 재요청을 보냈는데도 에러가 발생한 경우 재귀적으로 loop가 발생할 수 있기 때문에 이를 방지하기 위한 주석 2번 부분처럼 config.sent를 true로 설정*/
    config.sent = true;

    // refresh 요청
    // TODO: refresh 중복요청 문제 해결 필요 (참고:https://gusrb3164.github.io/web/2022/08/07/refresh-with-axios-for-client/)
    const { data } = await auth.refresh();

    const token = data.access;
    localStorage.setItem('access_token', token);
    localStorage.setItem('access_expiration', moment().add(30, 'minute').format('yyyy-MM-DD HH:mm:ss'));

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return axios(config);
  }
);

export default Api;

/api/refresh

import isExpired from '@utils/isExpired';
import auth from './accountAPI';

const goToLogin = () => {
  alert('로그인이 필요합니다.');
  window.location.replace('/login');
};

const refresh = async (config) => {
  const expireAt = localStorage.getItem('access_expiration');
  let token = localStorage.getItem('access_token');

  if (!expireAt) {
    console.log(`accessToken doesn't exist`);
    goToLogin();
    return config;
  }
  if (isExpired('access_expiration')) {
    console.log(`accessToken expired`);
    goToLogin();
    return config;
  }

  if (config.headers) {
    config.headers.Authorization = `Bearer ${String(token)}`;
  }
  return config;
};

const refreshErrorHandle = (err) => {
  console.log('refreshErrorHandle', err);
  if (err !== null) {
    auth.logout().catch((err) => {
      console.error(err);
    });
    goToLogin();
  }
};

export { refresh, refreshErrorHandle };

login시 프론트에서 할 일

		// 로그인
		auth
			.login({ username, password })
			.then(res => {
				console.log('login sucess', res);
				localStorage.setItem('access_token', res.data.access);
				localStorage.setItem(
					'access_expiration',
					moment().add(30, 'minute').format('yyyy-MM-DD HH:mm:ss'),
				);

				window.location.href = '/';
			})
			.catch(err => {
				console.log(err);
				if (err.response.status == 400) {
					alert('유효하지 않은 회원정보입니다.');
				}
			});

  • 로그인 성공시 로컬스토리지에 토큰을 저장해줘야한다.

※ django rest framework를 사용할 때..

/api/accountAPI.js

import axios from './config';
import Api from './api';

export default {
	idCheck(data) {
		return axios.post('/api/accounts/id-availability-check', data);
	},
	signUp(data) {
		return axios.post('/api/accounts/dj-rest-auth/registration', data);
	},
	login(data) {
		return axios.post('/api/accounts/dj-rest-auth/login/', data);
	},
	logout(data) {
		return Api.post('/api/accounts/dj-rest-auth/logout/', data);
	},
	getUser() {
		return Api.get('/api/accounts/dj-rest-auth/user/');
	},
	refresh() {
		return axios.post(`/api/accounts/dj-rest-auth/token/refresh/`);
	},
};
profile
강하고 담대하라 두려워하지 말라

0개의 댓글