Logo

자바스크립트의 URLSearchParams로 쿼리 스트링 다루기

지난 포스팅에서는 URL API의 URL을 사용하여 좀 더 안전하게 웹 주소 다루는 방법에 대해서 살펴보았는데요.

이번 포스팅에서는 URL API에서 제공하는 또 다른 유용한 기능인 URLSearchParams를 이용하여 웹 주소의 구성 요소 중에서도 가장 다루기 어려운 쿼리 스트링(Query String) 다루는 방법에 대해서 알아보겠습니다.

URLSearchParams 객체의 필요성

자바스크립트의 URLSearchParams에 대해서 본격적으로 배우기 전에 먼저 URL의 쿼리 스트링에 대해서 짚고 넘어가는 게 좋을 것 같아요.

소위 검색 파라미터(search parameters)라고도 불리는 쿼리 스트링은 URL에서 경로명(pathname) 바로 다음에 나오는 ? 기호로 시작하는 문자열인데요. 비단 검색 뿐만 아니라 필터링(filter), 페이지네이션(pagination), 정렬(sort) 등 다양한 용도로 사용됩니다. 보통 웹 서버에서는 URL의 쿼리 스트링을 분석하여 요청한 리소스를 응답하기 전에 다양한 추가 작업을 수행하게 됩니다.

쿼리 스트링에는 ?key1=value1&key2=value2&... 형태로 여러 개의 키와 값의 쌍을 & 기호로 구분하여 매개변수를 명시할 수 있는데요. 매개변수의 개수가 많아지면 사람의 눈으로 읽기가 쉽지 않고 매개변수에 다국어나 특수 문자가 포함되어 있으면 인코딩도 신경을 써줘야 하죠.

게다가 URL 명세에 따르면 쿼리 스트링은 ?key=value1&key=value2와 같이 동일한 키에 여러 개의 값을 할당하는 것도 허용하는데요. 이 부분도 처리할 때 경계 조건을 잘 고려하지 않으면 버그로 이어지기 쉽습니다.

예전에는 자바스크립트에서 쿼리 스트링을 마치 일반 문자열을 다루듯이 다루다가 실수하는 경우가 많어요. 그래서 버그가 발생하지 않으려면 개발자가 적지 않은 노력과 주의를 기울여야 했죠. 하지만 요즘에는 URL API에서 제공하는 URLSearchParams를 사용하여 좀 더 쉽고 안전하게 쿼리 스트링을 다룰 수 있게 되었습니다.

URLSearchParams 객체의 생성

URLSearchParams 전역 클래스의 생성자는 여러 형태의 값을 인자로 받을 수 있습니다.

우선 파라미터의 키와 값의 쌍으로 이루어진 2차원 배열을 넘길 수 있고요.

new URLSearchParams([
  ["mode", "dark"],
  ["page", 1],
  ["draft", false],
  ["sort", "email"],
  ["sort", "date"],
]);

또한 쿼리 스트링을 있는 그대로 문자열의 형태로 넘길 수도 있습니다.

new URLSearchParams("?mode=dark&page=1&draft=false&sort=email&sort=date");

참고로 이 때는 맨 앞에 ? 기호는 생략이 가능합니다.

new URLSearchParams("mode=dark&page=1&draft=false&sort=email&sort=date");

뿐만 아니라 인자로 아무것도 넘기지 않고 우선 빈 객체를 생성한 후 아래에서 배울 메서드를 통해서 나중에 파라미터를 추가할 수도 있습니다.

new URLSearchParams();

URLSearchParams 객체의 속성

URLSearchParams 객체에는 size 속성이 있는데요. 이 속성을 통해 쿼리 스트링에 얼마나 많은 매개변수가 들어있는지 알 수 있습니다.

const searchParams = new URLSearchParams("mode=dark&page=1&draft=false");
searchParams.size; // 3

그런데 여기서 한 가지 주의할 부분은 동일한 키에 여러 개의 값이 주어진 경우, 값의 개수를 기준으로 size 속성이 계산된다는 것입니다.

const searchParams = new URLSearchParams("sort=date&sort=email");
searchParams.size; // 2

따라서 유일한 키의 개수가 필요하시다면 Set을 이용해서 직접 구해야합니다.

new Set(searchParams.keys()).size; // 1

자바스크립트에서 고유한 값들의 집합을 다루는 자료구조인 세트(Set)에 대해서는 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

URLSearchParams 객체의 메서드

URLSearchParams 객체를 사용하다 보면 결국 문자열로 다시 변환해야 할 일이 자주 생기는데요. 이 때는 toString() 메서드를 사용합니다.

const searchParams = new URLSearchParams();
searchParams.toString(); // ''
const searchParams = new URLSearchParams([
  ["mode", "dark"],
  ["page", 1],
  ["draft", false],
]);
searchParams.toString(); // 'mode=dark&page=1&draft=false'

위에 보시다시피 toString() 메서드는 쿼리 스트링 맨 앞에 붙는 ? 기호는 생략하는데요. 따라서 URL 문자열을 직접 만든는 경우에는 주의해야합니다. 물론 지난 포스팅에서 배운 URL 객체를 사용하신다면 이런 부분에 대해서 걱정할 필요가 없겠죠?

URLSearchParams 객체는 쿼리 스트링의 매개변수를 읽거나 쓸 수 있도록 다양한 메서드를 제공하고 있습니다.

우선 append() 메서드를 이용하여 파라미터를 하나씩 추가할 수 있습니다.

const searchParams = new URLSearchParams();
searchParams.append("mode", "dark");
searchParams.append("page", 1);
searchParams.append("draft", false);
searchParams.append("sort", "email");
searchParams.append("sort", "date");
searchParams.toString(); // 'mode=dark&page=1&draft=false&sort=email&sort=date'

얼핏 보면 append()와 비슷해보이는 set()이라는 메서드도 있는데요. 기존 파라미터 키에 새로운 값을 추가하는 append()와 달리 set()은 기존 값을 지워버리고 새로운 값을 추가합니다.

const searchParams = new URLSearchParams();
searchParams.set("mode", "dark");
searchParams.set("page", 1);
searchParams.set("draft", false);
searchParams.set("sort", "email");
searchParams.set("sort", "date");
searchParams.toString(); // 'mode=dark&page=1&sort=date&draft=false'

위에 보시면 sort 키의 값으로 두 번째 값으로 설정한 date만 있고, 첫 번째 값으로 설정한 email는 사라진 것을 볼 수 있습니다.

URLSearchParams 객체에 저장되어 있는 값을 읽을 때는 get()getAll() 메서드를 사용할 수 있는데요. 하나의 키에 여러 개의 값이 저장되어 있을 때 get()은 첫 번째 값만 반환하는 반면에 getAll()은 모든 값을 배열로 반환합니다.

searchParams.get("mode"); // 'dark
searchParams.getAll("mode"); // [ 'dark' ]
searchParams.getAll("page"); // [ '1' ]
searchParams.get("draft"); // 'false'
searchParams.get("sort"); // 'email'
searchParams.getAll("sort"); // [ 'email', 'date' ]

쿼리 스트링 안에서는 모든 값이 문자열로 취급되기 때문에 숫자 1인 아닌 문자열 "1"이 얻어지고, 불리언 false가 아닌 문자열 "false"가 얻어지는 부분 조심하시기 바랍니다.

URLSearchParams 객체에 저장되어 있는 파라미터는 for...of 문법을 사용하여 쉽게 순회할 수 있습니다.

for (const param of searchParams) {
  console.log(param);
}
['mode', 'dark']
['page', '1']
['draft', 'false']
['sort', 'email']
['sort', 'date']

ES6의 구조 분해(Destructuring) 문법을 활용하면 키와 값을 각각 다른 변수에 할당할 수도 있겠죠?

for (const [key, value] of searchParams) {
  console.log(`${key}: ${value}`);
}
mode: dark
page: 1
draft: false
sort: email
sort: date

URLSearchParams 객체에서 파라미터를 삭제하고 싶을 때는 delete() 메서드를 사용하는데요. 인자로 키를 넘기면 해당 키에 해당하는 모든 값이 함께 삭제됩니다.

searchParams.delete("draft");
searchParams.toString(); // 'mode=dark&page=1&sort=email&sort=date'
searchParams.delete("sort");
searchParams.toString(); // 'mode=dark&page=1'

단순히 특정 파라미터의 존재 여부를 알고 싶을 때는 has() 메서드를 사용합니다.

searchParams.has("page"); // true
searchParams.has("x"); // false

매개변수를 오름차순으로 정렬할 수도 있습니다. 키가 1차 기준이 되고 값이 2차 기준이 됩니다.

searchParams.sort();
searchParams.toString(); // 'draft=false&mode=dark&page=1&sort=date&sort=email'

URL과 URLSearchParams 함께 사용

기존의 URL의 쿼리 스트링을 접근하거나 조작하고 싶을 때는 지난 포스팅에서 배운 URL 객체와 함께 URLSearchParams 객체를 쓰면 유용한데요. URL 객체의 search 속성에는 쿼리 스트링이 문자열로 저장되어 있고, searchParams 속성에는 쿼리 스트링이 URLSearchParams 객체로 저장이 되어 있습니다.

const url = new URL("https://example.org:8080/foo/bar?q=baz#bang");
url.search; // '?q=baz'
url.searchParams; // URLSearchParams {size: 1}

url.searchParams.get("q"); // 'baz'
url.searchParams.set("q", "updated");
url.searchParams.append("r", 2);
url.searchParams.append("r", false);

url.toString(); // 'https://example.org:8080/foo/bar?q=updated&r=2&r=false#bang'

URL 객체의 searchParams 속성에 접근하여 위와 같이 쿼리 스트링을 간편하게 조작할 수 있습니다.

한 가지 범하기 쉬운 실수는 새로운 URLSearchParams 객체를 바로 URL 객체의 searchParams 속성에 할당하는 것입니다.

const url = new URL("https://example.org:8080/foo/bar?q=baz#bang");
url.searchParams = new URLSearchParams("q=updated&r=2&r=false");
url.toString(); // 'https://example.org:8080/foo/bar?q=baz#bang'

위에 보시다시피 새로운 쿼리 스트링이 반영되지 않는데요. 그 이유는 URL 객체의 searchParams 속성은 읽기 전용이기 때문입니다. 따라서 URL 객체의 searchParams 속성을 읽은 후에 그 안에 들어있는 매개변수를 조작할 수는 있지만 아예 새로운 URLSearchParams 객체로 대체할 수는 없습니다.

대신에 URL 객체 search 속성은 쓰기가 가능하기 때문에 아래와 같이 URLSearchParams 객체를 문자열로 변환한 후에 URL 객체 search 속성에 할당해주면 되겠습니다.

const url = new URL("https://example.org:8080/foo/bar?q=baz#bang");
url.search = new URLSearchParams("q=updated&r=2&r=false").toString();
url.toString(); // 'https://example.org:8080/foo/bar?q=updated&r=2&r=false#bang'

마치면서

이상으로 자바스크립트로 쿼리 스트링을 다룰 때 아주 유용하게 쓸 수 있는 URL API의 URLSearchParams에 대해서 자세히 살펴보았습니다. 참고로 URLSearchParams 클래스는 브라우저 뿐만 아니라 Node.jsBun에서도 지원하는 웹 표준 API이기 때문에 프론트엔드 백엔드 가리지 않고 활용할 수 있습니다.

본 포스팅에서 다룬 URLSearchParams를 잘 활용하셔서 쿼리 스트링을 좀 더 안전하게 다루시는데 도움이 되었으면 좋겠습니다.