「パス結合」で何を楽にしたいのか
「パス結合」は、URL のパスを安全にくっつけるユーティリティです。
"/api" と "users" を結合して "/api/users" にしたい
"/api/" と "/users" を結合しても "//" になってほしくない
こういう「/ の重複」や「/ の抜け」を、毎回自分で気にしたくないからこそ、
“パスをいい感じにくっつけてくれる関数”を持っておく価値があります。
よくある「パス結合の罠」を先に見ておく
素直に + でつなぐとこうなる
例えば、こんなコードを書きがちです。
const base = "/api";
const path = "/users";
const url = base + "/" + path;
// "/api//users" になってしまう
JavaScript逆に、こう書くと今度は / が足りないこともあります。
const base = "/api/";
const path = "users";
const url = base + path;
// "/api/users"(このケースはたまたま正しい)
JavaScript問題は、「base 側に / が付いているか」「path 側の先頭に / があるか」を
呼び出し側が毎回意識しないといけないことです。
これをユーティリティに任せてしまうのが「パス結合」です。
一番基本的なパス結合ユーティリティ
シンプル版 joinPath の実装
まずは、「複数のパス断片を受け取って、1 本のパスにする」関数を作ります。
function joinPath(...parts) {
const filtered = parts
.filter((p) => p != null && p !== "")
.map((p) => String(p));
if (filtered.length === 0) {
return "";
}
const normalized = filtered.map((p, index) => {
let s = p;
if (index === 0) {
s = s.replace(/\/+$/g, "");
} else {
s = s.replace(/^\/+/g, "").replace(/\/+$/g, "");
}
return s;
});
let result = normalized.join("/");
if (!result.startsWith("/")) {
result = "/" + result;
}
return result;
}
JavaScript重要なポイントをかみ砕いて説明する
ここはしっかり理解してほしいところです。
1. null / 空文字は無視する
const filtered = parts
.filter((p) => p != null && p !== "")
.map((p) => String(p));
JavaScriptnull や undefined、空文字が混ざっていても、
「なかったこと」にして結合できるようにしています。
joinPath("/api", null, "users");
// "/api/users"
JavaScriptこうしておくと、「条件によってパスを足したり引いたり」するときに扱いやすくなります。
2. 先頭の要素とそれ以外でルールを変える
const normalized = filtered.map((p, index) => {
let s = p;
if (index === 0) {
s = s.replace(/\/+$/g, "");
} else {
s = s.replace(/^\/+/g, "").replace(/\/+$/g, "");
}
return s;
});
JavaScriptここが一番の肝です。
先頭の要素(index === 0)
→ 末尾の / だけ消す(先頭の / は残す)
2 個目以降の要素
→ 先頭と末尾の / を両方消す
こうすることで、例えば次のような入力が全部きれいに揃います。
joinPath("/api", "users"); // "/api/users"
joinPath("/api/", "users"); // "/api/users"
joinPath("/api", "/users"); // "/api/users"
joinPath("/api/", "/users/"); // "/api/users"
JavaScript「先頭のパスは / から始まっていてほしい」
「途中のパスは / なしでつなぎたい」
というルールを、ここで強制しています。
3. 最後に / で join して、先頭 / を保証する
let result = normalized.join("/");
if (!result.startsWith("/")) {
result = "/" + result;
}
JavaScriptjoin("/") で ["/api", "users"] → "/api/users" のように結合します。
さらに、もし先頭に / がなければ付けておきます。
これで、常に「絶対パス」の形(/xxx/yyy)で返ってくるようになります。
実際の動きを例で確認する
よくあるパターンを全部試してみる
joinPath("/api", "users");
// "/api/users"
joinPath("/api/", "users");
// "/api/users"
joinPath("/api", "/users");
// "/api/users"
joinPath("/api/", "/users/");
// "/api/users"
joinPath("/api", "v1", "users", "123");
// "/api/v1/users/123"
joinPath("/api/", null, "users", "", "123/");
// "/api/users/123"
JavaScriptどんな組み合わせでも、「/ の重複なし」「末尾に余計な / なし」で揃っているのが分かると思います。
ベース URL と組み合わせるときの考え方
ドメイン込みの URL にも使いたい場合
さっきの joinPath は、「パス部分」だけを想定していました。
joinPath("/api", "users"); // "/api/users"
JavaScriptもし、https://example.com のようなベース URL と組み合わせたい場合は、
「ドメイン部分」と「パス部分」を分けて考えるときれいです。
function joinUrl(baseUrl, ...paths) {
const base = String(baseUrl).replace(/\/+$/g, "");
const path = joinPath(...paths);
return base + path;
}
JavaScript使い方はこうです。
joinUrl("https://example.com", "api", "users");
// "https://example.com/api/users"
joinUrl("https://example.com/", "/api/", "/users/");
// "https://example.com/api/users"
JavaScriptここでのポイントは、
- ベース URL の末尾の
/は消しておく - パス部分は
joinPathに任せる
という責務分担です。
実務での使いどころと設計のポイント
「呼び出し側に / の有無を意識させない」
パス結合ユーティリティの一番の価値は、
「呼び出し側が / の有無を気にしなくてよくなる」ことです。
// 悪い例:呼び出し側が / を意識し続ける
const url = "/api/" + userId + "/profile";
// 良い例:ユーティリティに任せる
const url = joinPath("/api", userId, "profile");
JavaScript後者の方が、
- 読みやすい
/の付け忘れ・付けすぎが起きない- パスの構造が一目で分かる
というメリットがあります。
「パス結合」と「クエリ生成」を分けて考える
URL 全体は、ざっくりこう分けて考えると整理しやすいです。
- パス部分 →
joinPath/joinUrl - クエリ部分 →
toQueryString(前にやったやつ)
const path = joinPath("/api", "users", userId);
const qs = toQueryString({ include: "profile" });
const url = path + qs;
// "/api/users/123?include=profile"
JavaScriptこうしておくと、
- パスの構造を変えたいときは
joinPathの引数を変える - クエリの中身を変えたいときは
toQueryStringの引数を変える
という、きれいな責務分担になります。
ちょっとだけ手を動かしてみる
コンソールで、次のあたりを試してみてください。
joinPath("/api", "users");
joinPath("/api/", "users");
joinPath("/api", "/users");
joinPath("/api/", "/users/");
joinUrl("https://example.com", "api", "users", "123");
joinUrl("https://example.com/", "/api/", "/users/");
JavaScript「どんな入力でも同じ形に整えてくれる感覚」を、自分の目で確かめてみてください。
そのうえで、自分のプロジェクトに
export function joinPath(...parts) { ... }
export function joinUrl(baseUrl, ...paths) { ... }
JavaScriptを置いて、
- パスだけ結合したい →
joinPath - ドメイン込みで URL を作りたい →
joinUrl
という使い分けをルール化してみてください。
それだけで、あなたの URL 組み立てコードは、
場当たり的な文字列連結から、読みやすくて安全な“業務レベルのパス結合ユーティリティ”に一段レベルアップします。
