JavaScript Tips | 文字列ユーティリティ:URL 系 - クエリ生成

JavaScript JavaScript
スポンサーリンク

まず「クエリ生成」で何を自動化したいのか

ここで言う「クエリ生成」は、オブジェクト(連想配列)から ?key=value&key2=value2 みたいなクエリ文字列を、安全に・楽に作るユーティリティのことです。

検索条件やページ番号、ソート条件などを URL に付けたいとき、毎回手で

"/search?q=" + encodeURIComponent(q) + "&page=" + page
JavaScript

と書いていると、すぐにぐちゃぐちゃになります。

だから、

toQueryString({ q: "JavaScript 入門", page: 2, sort: "created_at desc" });
// "?q=JavaScript%20%E5%85%A5%E9%96%80&page=2&sort=created_at%20desc"
JavaScript

のように、「オブジェクト → クエリ文字列」を一発でやってくれる関数を持っておくと、実務でめちゃくちゃ効きます。


クエリ文字列の基本ルールをざっくり整理する

形はシンプル、でも落とし穴が多い

クエリ文字列の基本形はこうです。

?key1=value1&key2=value2&key3=value3

ここで大事なのは 2 つです。

  1. ? は最初の 1 回だけ
  2. & で「パラメータ同士」をつなぐ

そして、key と value は URL エンコードしておく必要がある、というのが一番のポイントです。

q = "JavaScript 入門"
"q=" + encodeURIComponent("JavaScript 入門")
"q=JavaScript%20%E5%85%A5%E9%96%80"
JavaScript

エンコードしないと、スペースや &= が混ざったときに壊れます。


実務で使える「クエリ生成」ユーティリティの基本形

オブジェクト → ?key=value&... を作る

まずは、シンプルな版から作ります。

function toQueryString(params) {
  if (!params || typeof params !== "object") {
    return "";
  }

  const parts = [];

  for (const [key, value] of Object.entries(params)) {
    if (value == null) {
      // null / undefined はクエリに含めない
      continue;
    }

    const encodedKey = encodeURIComponent(key);
    const encodedValue = encodeURIComponent(String(value));

    parts.push(`${encodedKey}=${encodedValue}`);
  }

  if (parts.length === 0) {
    return "";
  }

  return "?" + parts.join("&");
}
JavaScript

重要ポイントをかみ砕いて説明する

ここはしっかり理解してほしいところです。

1. params は「オブジェクト前提」にする

if (!params || typeof params !== "object") {
  return "";
}
JavaScript

クエリ生成は「キーと値のセット」が前提なので、
文字列や数値が来たら何もせず空文字を返す、という割り切りにしています。

2. null / undefined はクエリに含めない

if (value == null) continue;
JavaScript

== nullnullundefined の両方を拾えます。

?page=&q= のような「空のパラメータ」を付けると、
サーバー側の解釈がややこしくなることが多いので、
「値がないものはそもそも付けない」という方針にしておくとシンプルです。

3. key も value も必ず encodeURIComponent

const encodedKey = encodeURIComponent(key);
const encodedValue = encodeURIComponent(String(value));
JavaScript

ここが一番大事です。

  • key にも -. 以外の記号が入ることがある
  • value にはスペース、日本語、&= など何でも入る

ので、両方とも encodeURIComponent しておくのが安全です。

4. 先頭の ? をユーティリティ側で付ける

return "?" + parts.join("&");
JavaScript

呼び出し側で毎回 ? を付けるようにすると、
"/search" + "?" + toQueryString(...) みたいに二重になったり、
? を付け忘れたりしがちです。

「クエリ文字列を作る関数は、? から始まる文字列を返す」と決めておくと、
使う側のコードがスッキリします。


実際の使用例でイメージを固める

検索画面の URL を組み立てる

const params = {
  q: "JavaScript 入門",
  page: 2,
  sort: "created_at desc",
};

const url = "/search" + toQueryString(params);
// "/search?q=JavaScript%20%E5%85%A5%E9%96%80&page=2&sort=created_at%20desc"
JavaScript

ここで、スペースや日本語がちゃんと %xx 形式に変わっていることがポイントです。

条件によってパラメータを付けたり消したり

const params = {
  q: keyword || undefined,
  page: currentPage > 1 ? currentPage : undefined,
  sort: sortKey || undefined,
};

const url = "/search" + toQueryString(params);
JavaScript

undefined にしておけば、toQueryString 側でスキップされるので、
「指定されていない条件は URL に出さない」という挙動を簡単に実現できます。


配列や複数値をどう扱うか(少し応用)

tags=js&tags=ts のように同じキーを複数回出したい場合

タグ検索などで、同じキーを複数回出したいことがあります。

?tag=js&tag=ts&tag=node

これをサポートしたい場合は、配列を特別扱いするように拡張できます。

function toQueryStringWithArray(params) {
  if (!params || typeof params !== "object") {
    return "";
  }

  const parts = [];

  for (const [key, value] of Object.entries(params)) {
    if (value == null) continue;

    const encodedKey = encodeURIComponent(key);

    if (Array.isArray(value)) {
      for (const v of value) {
        if (v == null) continue;
        const encodedValue = encodeURIComponent(String(v));
        parts.push(`${encodedKey}=${encodedValue}`);
      }
    } else {
      const encodedValue = encodeURIComponent(String(value));
      parts.push(`${encodedKey}=${encodedValue}`);
    }
  }

  if (parts.length === 0) {
    return "";
  }

  return "?" + parts.join("&");
}
JavaScript

使い方はこうです。

toQueryStringWithArray({
  q: "js",
  tag: ["frontend", "node"],
});
// "?q=js&tag=frontend&tag=node"
JavaScript

「配列は同じキーを複数回出す」というルールを決めておくと、
サーバー側でも扱いやすくなります。


設計として意識してほしいこと

「エンコードの責務」をユーティリティに閉じ込める

クエリ生成で一番やりがちなのは、
画面ごとにバラバラに

"?q=" + encodeURIComponent(q) + "&page=" + page
JavaScript

と書き始めてしまうことです。

そうすると、

  • ある画面では page をエンコードしていない
  • 別の画面では sort を付け忘れている
  • どこかで ? が二重になっている

といった「地味だけど面倒なバグ」が増えます。

だからこそ、

export function toQueryString(params) { ... }
export function toQueryStringWithArray(params) { ... }
JavaScript

のようなユーティリティを 1 箇所に置いて、
「クエリ文字列を作るときは必ずここを通す」と決めてしまうのが、業務レベルの設計です。

「クエリ生成」と「URL 全体の組み立て」を分ける

URL 全体はこう分けて考えるとスッキリします。

  • パス部分:"/search" など、自分で書く
  • クエリ部分:toQueryString(params) に任せる
const baseUrl = "/search";
const qs = toQueryString(params);
const url = baseUrl + qs;
JavaScript

こうしておくと、

  • ベース URL を変えたいときは baseUrl だけ変えればいい
  • クエリの中身を変えたいときは params だけ変えればいい

という、きれいな責務分担になります。


ちょっとだけ手を動かしてみる

コンソールで、次のあたりを試してみてください。

toQueryString({ q: "JavaScript 入門", page: 2 });
toQueryString({ q: "A&B", sort: "created_at desc" });
toQueryStringWithArray({ tag: ["js", "ts"], q: "frontend" });
JavaScript

出てきた文字列を眺めながら、

  • スペースや & がどう %xx に変わっているか
  • 配列がどう複数のパラメータに展開されているか

を確認してみてください。

そのうえで、自分のプロジェクトに

export function toQueryString(...) { ... }
export function toQueryStringWithArray(...) { ... }
JavaScript

を置いて、

「クエリを作るときは必ずこの関数を通す」

というルールにしてしまえば、
あなたの URL 周りのコードは、
場当たり的な文字列連結から、意図と安全性を両立した“業務レベルのクエリ生成ユーティリティ”に一段レベルアップします。

タイトルとURLをコピーしました