[React] qs 사용하기 (feat.query-string)

Lui.Slki·2026년 3월 13일

React

목록 보기
3/5

qs란,

객체(object)를 query string으로 변환하거나, query string 을 다시 객체로 파싱(parse)하는 라이브러리다.

쉽게 말하면 프론트에서 이렇게 관리하는 데이터를

{
  keyword: "전공",
  styleTagIds: [1, 2],
  instIds: [3, 7],
}

이런 URL 쿼리 형태로 바꿔주는 역할을 한다.

keyword=%ED%94%8C%EB%A3%BB&styleTagIds=1&styleTagIds=2&instIds=3&instIds=7

React에서 검색 필터를 구현하다 보면
검색 조건은 보통 객체 형태로 관리하지만, GET 요청을 보낼 때는 결국 query string 형태로 바꿔야한다.

처음에느 단순해 보이지만, 필터가 많아지고 배열 형태의 값이 생기면 직접 조립하는 코드가 지저분해진다.


qs를 쓰기 전: 직접 query string을 조립하던 코드(예시)

처음에는 URLSearchParams 로도 충분하다고 생각했다.

export const searchLessonReq = async (params: {
  keyword?: string;
  mode?: string;
  styleTagIds?: number[];
  instCategory?: string;
  instIds?: number[];
  from?: string;
  to?: string;
  daysOfWeek?: number[];
  timeParts?: string[];
}) => {
  const searchParams = new URLSearchParams();

  if (params.keyword) searchParams.append("keyword", params.keyword);
  if (params.mode) searchParams.append("mode", params.mode);
  if (params.instCategory) {
    searchParams.append("instCategory", params.instCategory);
  }
  if (params.from) searchParams.append("from", params.from);
  if (params.to) searchParams.append("to", params.to);

  params.styleTagIds?.forEach((id) => {
    searchParams.append("styleTagIds", String(id));
  });

  params.instIds?.forEach((id) => {
    searchParams.append("instIds", String(id));
  });

  params.daysOfWeek?.forEach((day) => {
    searchParams.append("daysOfWeek", String(day));
  });

  params.timeParts?.forEach((tp) => {
    searchParams.append("timeParts", tp);
  });

  return axios.get(`/api/lessons?${searchParams.toString()}`);
};

동작은 하지만,

  • 조건이 하나 추가될 때마다 append() 를 직접 써야 한다
  • 배열 필드는 전부 foreach 로 반복해야 한다
  • 빈 값, 없는 값 처리까지 계속 신경 써야 한다
  • 필터가 많아질수록 코드가 길어진다
  • 결국 "검색 요청"보다 "문자열 조립" 코드가 더 눈에 들어온다

즉, 비즈니스 로직보다 직렬화 코드가 존재감이 더 커지는 상황이 발생한다.


qs 적용 후(예시)

해당 문제를 줄이기 위해 qs 를 도입했다.

npm i qs

적용 후 코드는 이렇게 바뀌었다.

export const searchLessonReq = async (params: {
  keyword?: string;
  mode?: string;
  styleTagIds?: number[];
  instCategory?: string;
  instIds?: number[];
  from?: string;
  to?: string;
  daysOfWeek?: number[];
  timeParts?: string[];
}) => {
  const query = qs.stringify(params, {
    arrayFormat: "repeat",
    skipNulls: true,
  });

  return axios.get(`/api/lessons?${query}`);
};

훨씬 간단해졌다.

이제는 query string을 직접 조립하지 않고,
검색 조건 객체를 만들고 qs.stringify()에 넘기는 것만으로 끝낸다.

이렇게 되면 조건이 늘어나도 params 만 관리하면 되고,
직렬화 방식은 라이브러리에 맡길 수 있다.


arrayFormat: "repeat" 를 사용한 이유

배열 파라미터가 많은 검색 API를 구현한다고 가정하자.

예를들어 styleTagIds: [1, 2] 를 query string으로 만들 때, 배열은 여러 방식으로 표현할 수 있다.

repat 방식

styleTagIds=1&styleTagIds=2

braket 방식

styleTagIds[]=1&styleTagIds[]=2

comma 방식

styleTagIds=1,2

이번에는 이 중에서 repeat 방식을 사용했다.

qs.stringify(
  { styleTagIds: [1, 2] },
  { arrayFormat: "repeat" }
);

해당 방식을 선택한 이유는 Spring 백엔드에서 다음과 같이 받고 있었기 때문이다.

@RequestParam(required = false) List<Long> styleTagIds
@RequestParam(required = false) List<Long> instIds
@RequestParam(required = false) List<Integer> daysOfWeek
@RequestParam(required = false) List<String> timeParts

즉, 프론트에서 같은 key를 반복하는 형태로 보내면 Spring이 List<T> 로 자연스럽게 바인딩할 수 있다.


skipNulls: true 도 같이 유용했다

검색 필터는 항상 모든 값이 다 들어오지 않는다.

예를 들면 이런 경우가 있다.

const params = {
  keyword: undefined,
  mode: undefined,
  styleTagIds: [1, 2],
  instIds: undefined,
};

이런 상태에서 필요 없는 값까지 query string에 포함 시키고 싶지는 않았다.

그래서 skipNulls: true 옵션도 같이 사용했다.

const query = qs.stringify(params, {
  arrayFormat: "repeat",
  skipNulls: true,
});

이렇게 하면 null 이나 undefined인 값은 제외되고,
실제로 검색에 들어온 값만 query string에 포함된다.

결과적으로 요청 자체가 더 깔끔해진다.


query-string & qs

비슷한 역할을 하는 라이브러리로 query-string도 있다.

둘 다 객체와 query string을 다룰 수 있지만, 사용감은 조금 다르다.

query-string

  • 비교적 가볍다
  • 프론트에서 URL 쿼리를 읽고 쓰기에 깔끔한 편이다
  • 단순한 query string 처리에는 충분히 편하다

qs

  • 배열 직렬화 옵션이 더 유연하다
  • 중첩 객체나 복잡한 구조 대응력이 더 좋다
  • 백엔드와 맞물리는 직렬화 제어에 더 강하다

이번 경우에는 단순히 주소창 쿼리를 다루는 것보다
배열 파라미터를 Spring 형식에 맞게 안정적으로 보내는 것이 더 중요했기 때문에 본인은 qs를 션택했다.


정리

이번에 qs를 도입하면서 좋아진 점은 분명했다.

  • query string 조립 코드가 짧아졌다
  • 배열 필드를 직접 반복문으로 처리하지 않아도 된다
  • 검색 조건이 늘어나도 params 객체만 관리하면 된다
  • Spring 백엔드와 맞는 형태로 배열 파라미터를 보낼 수 있다
  • null, undifined 값도 깔끔하게 제외할 수 있다

검색 필터가 많아질수록 문제는 조건관리보다
그 조건을 query string으로 어떻게 직렬화할 것인가 쪽에서 더 자주 터진다.

이번에는 qs를 통해 그 부분을 훨씬 단순하게 정리할 수 있었다.


마무리

처음엔 URLSearchParams로도 충분해 보였다.
하지만 배열 필터가 늘어나고, 백엔드와 맞는 형태까지 고려해야 하니 코드가 금방 길어졌다.

qs를 도입한 뒤에는
직접 문자열을 조립하는 대신 객첸 중심으로 조건을 관리하고, 직렬화는 라이브러리에 맡기는 구조로 정리할 수 있었다.

0개의 댓글