Skip to main content

Command Palette

Search for a command to run...

fetch vs axios

fetch와 axios가 어떻게 동작하는지 코드를 통해 알아봤습니다.

Updated
•3 min read•View as Markdown
fetch vs axios

시작하기 전에

API 요청을 위해 fetch 함수를 사용하다가 네트워크 응답으로 **“body must be object”**라는 에러를 마주치게 되었다.

예전에 axios를 사용했을 때는 몰랐는데, fetch를 사용할 때는 header 옵션을 직접 설정해줘야 한다는 것을 알게 됐고,

fetch는 왜 수동으로 직렬화 해야하며, axios는 왜 직접 설정할 필요가 없는지 코드 분석을 통해 비교해보고자 글을 쓰려고 한다.

자바스크립트 객체는 네트워크 전송이 안된다?

로그인 진행 시에 email, password가 필요하여 그대로 요청을 보냈는데, 응답으로 에러메시지가 나왔다.

구글링 및 API 문서를 확인하다보니 fetch 사용 시 요청 데이터는 json 형태이므로 headers에 다음과 같이 명시해줘야 한다는 것이다.

headers: {"Content-Type": "application/json"}

당장 alert({name:'철수',age:30}) 만 콘솔에 쳐서 확인해보면

[object Object] 라는 요상한(?) 객체가 나온다.

추측해보건대, 이렇게 객체가 변환되기 때문에 json 객체로 수동으로 변환해야 하는 것 같다.

[object Object]?

[object Object]로 변환되는 원리를 파악하기 위해 클로드와 함께 소스코드를 까보기로 했다.

ECMAScript를 살펴봤을 때

출처 : https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.prototype.tostring

위 스펙에 따라 일반 객체는 [object Object]라는 형태로 나오게 되는 것을 알 수 있다.

또한 v8 소스코드를 살펴보면 Torque라는 언어로 내장 함수를 구현했다고 한다.

transitioning javascript builtin ObjectPrototypeToString(
      js-implicit context: Context, receiver: JSAny)(): String {
    return ObjectToString(context, receiver);
  }

왜 일반 자바스크립트 객체는 네트워크 전송이 되지 않을까?

데이터를 네트워크로 보낼 때 왜 JSON 객체로 래핑해서 보내야 할까?

우선 네트워크는 텍스트 또는 바이트 스트림만 허용하고 자바스크립트 같이 자바스크립트 런타임에만 허용되는 객체는 보안상 허용하지 않는다고 한다.

이를 전송 가능한 객체로 변환하기 위해 직렬화 하는 과정이 필요하다.

const jsonString = JSON.stringify(user);
  // '{"name":"김철수","age":25,"friends":["영희","민수"]}'

그렇다면 fetch 함수를 통해 데이터를 JSON.stringify를 감싸면 되냐? 그게 끝이 아니다.
headers 객체를 통해 Content-type을 명시해주지 않으면 앞서 나왔던

{
    "success": false,
    "error": {
        "message": "body must be object",
        "statusCode": 400
    }
}

위 에러가 나오게 된다.

 const response = await fetch(
        `${import.meta.env.VITE_API_URL}/api/auth/login`,
        {
          method: "POST",
          body: JSON.stringify(data),
          // headers 설정 필요!
          headers: {
            "Content-Type": "application/json",
          },
        },
 );

Axios

이에 반해 axios는 fetch와 달리 데이터를 자동으로 직렬화 해준다고 하는데, 정말 그런지 알아보기로 했다.

// https://github.com/axios/axios/blob/v1.x/lib/defaults/index.js
function stringifySafely(rawValue, parser, encoder) {
  if (utils.isString(rawValue)) {
    try {
      (parser || JSON.parse)(rawValue);
      return utils.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        throw e;
      }
    }
  }

  return (encoder || JSON.stringify)(rawValue);
}

const defaults = {
  transitional: transitionalDefaults,
  adapter: ['xhr', 'http', 'fetch'],
  transformRequest: [function transformRequest(data, headers) {
    const contentType = headers.getContentType() || '';
    const hasJSONContentType = contentType.indexOf('application/json') > -1;
    const isObjectPayload = utils.isObject(data);

    if (isObjectPayload && utils.isHTMLForm(data)) {
      data = new FormData(data);
    }

    const isFormData = utils.isFormData(data);

    if (isFormData) {
      return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
    }

    if (utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data) ||
      utils.isReadableStream(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false);
      return data.toString();
    }

    let isFileList;

    if (isObjectPayload) {
      if (contentType.indexOf('application/x-www-form-urlencoded') > -1) {
        return toURLEncodedForm(data, this.formSerializer).toString();
      }

      if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
        const _FormData = this.env && this.env.FormData;

        return toFormData(
          isFileList ? {'files[]': data} : data,
          _FormData && new _FormData(),
          this.formSerializer
        );
      }
    }

    if (isObjectPayload || hasJSONContentType ) {
      headers.setContentType('application/json', false);
      return stringifySafely(data);
    }

    return data;
  }]

// https://github.com/axios/axios/blob/v1.x/lib/utils.js#L113
const isObject = (thing) => thing !== null && typeof thing === 'object';

해당 코드를 살펴보면 axios는 transformRequest를 통해 데이터를 자동으로 직렬화하고 headers 자동으로 설정한다.

예시 코드로 비교

fetch

const response = await fetch(
    `${import.meta.env.VITE_API_URL}/api/auth/login`,
    {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json",
    },
  },
);

axios

const res = await axios.post<LoginResponse>(
  `${import.meta.env.VITE_API_URL}/api/auth/login`,
  data
);

마무리

그동안 fetch와 axios를 단순히 API 요청 툴로만 활용하고 파볼 생각은 안하고 있었는데, 이번 에러를 만나게 됨으로써 해당 툴들이 어떻게 동작하는지 대략적으로나마 알게 되었고, 다음에는 fetch와 axios의 각 기능 및 장단점을 비교해 볼 예정이다.

More from this blog

shadcn UI의 checkbox는 왜 indeterminate이라는 상태가 존재하는가?

예전에 shadcn으로 checkbox UI를 만들어보다가 shadcn 코드베이스에 indeterminate 라는 상태가 있는 것을 보게 되었다. 체크 상태는 체크/체크 해제 밖에 없는 것 아닌가? 라고 생각했고, 한 번 indeterminate의 정체가 뭔지 살펴보기로 했다. 체크박스에는 체크와 해제 외에 세 번째 상태가 있다. 이 상태의 이름은 inde

Oct 10, 20269 min read9

코드제로투원 프로젝트 회고

약 6개월간 진행한 프로젝트를 2주 전에 끝마쳤다. 원래 사이드프로젝트에 대한 회고를 잘 작성하지 않는 편이어서, 이번에도 고민을 좀 했지만, 여태까지 프로젝트를 하면서 가장 많이 기억에 남은 기억이라 이렇게 회고를 작성하려고 한다. 참여하게 된 계기 올해 1월, 코드제로투원이라는 스터디 플랫폼 사이드프로젝트에 참여했다. 직전에는 자취생들을 위한 식단 추천

Jul 17, 20265 min read40
코드제로투원 프로젝트 회고

haseung-log

33 posts