はじめに

Webアプリケーションを開発していると、ユーザーの入力内容や設定情報をブラウザ側に保存しておきたい場面があります。たとえば、フォームの入力途中のデータを一時保存したり、ダークモードの設定を次回訪問時にも維持したりする場合です。

このようなクライアント側でのデータ保存を実現するのが、Web Storage APIが提供するlocalStoragesessionStorageです。どちらもブラウザにキーと値のペアを保存できる仕組みですが、データの有効期間に違いがあります。

本記事では、これらのブラウザストレージの基本的な使い方から、オブジェクトや配列を保存するためのJSON変換、セキュリティ上の注意点まで、初心者向けに具体的なコード例を交えて解説します。

Web Storage APIとは

Web Storageの概要

Web Storage APIは、ブラウザがキーと値のペアを保存できる仕組みを提供します。Cookieと比較して、より直感的にデータを扱えるのが特徴です。

MDN Web Docsでは、Web Storageを次のように説明しています。

ウェブストレージには、sessionStoragelocalStorageの2種類の仕組みがあります。どちらもCookieよりもストレージ容量が大きく(最大5MB程度)、サーバーへのデータ転送が発生しません。

localStorageとsessionStorageの違い

localStoragesessionStorageは、ほぼ同じAPIを持ちますが、データの保持期間が異なります。

特性 localStorage sessionStorage
データの有効期間 ブラウザを閉じても永続的に保持 タブやウィンドウを閉じると消去
タブ間の共有 同一オリジン内で共有される タブごとに独立
ストレージ容量 約5MB(ブラウザにより異なる) 約5MB(ブラウザにより異なる)
主な用途 ユーザー設定、永続的なデータ 一時的なフォームデータ、セッション情報

以下の図は、両者のライフサイクルの違いを示しています。

graph TB
    subgraph localStorage
        A[データ保存] --> B[ブラウザ再起動]
        B --> C[データ維持]
        C --> D[明示的に削除するまで保持]
    end
    
    subgraph sessionStorage
        E[データ保存] --> F[タブを閉じる]
        F --> G[データ消去]
    end
    
    style A fill:#4CAF50,color:#fff
    style D fill:#4CAF50,color:#fff
    style E fill:#2196F3,color:#fff
    style G fill:#f44336,color:#fff

オリジン(Origin)による分離

localStoragesessionStorageのデータは、オリジン(プロトコル + ホスト名 + ポート番号)ごとに分離されています。

1
2
3
4
5
// https://example.com のlocalStorage
localStorage.setItem("key", "value");

// http://example.com のlocalStorageは別のストレージ
// https://example.com:8080 のlocalStorageも別のストレージ

これにより、異なるWebサイト間でデータが共有されることはありません。

基本的な操作方法

データの保存(setItem)

setItem()メソッドを使用して、キーと値のペアを保存します。

1
2
3
4
5
// localStorageに保存
localStorage.setItem("username", "tanaka");

// sessionStorageに保存
sessionStorage.setItem("tempData", "一時的な値");

キーと値はどちらも文字列として保存されます。数値を渡した場合も、自動的に文字列に変換されます。

1
2
localStorage.setItem("count", 42);
console.log(typeof localStorage.getItem("count")); // "string"

データの取得(getItem)

getItem()メソッドを使用して、保存されているデータを取得します。

1
2
3
4
5
6
7
// localStorageから取得
const username = localStorage.getItem("username");
console.log(username); // "tanaka"

// sessionStorageから取得
const tempData = sessionStorage.getItem("tempData");
console.log(tempData); // "一時的な値"

存在しないキーを指定した場合は、nullが返されます。

1
2
const notExist = localStorage.getItem("存在しないキー");
console.log(notExist); // null

データの削除(removeItem)

removeItem()メソッドを使用して、特定のキーのデータを削除します。

1
2
3
4
5
// 特定のキーを削除
localStorage.removeItem("username");

// 削除後に取得すると null
console.log(localStorage.getItem("username")); // null

全データの削除(clear)

clear()メソッドを使用して、ストレージ内のすべてのデータを削除します。

1
2
3
4
5
// localStorageの全データを削除
localStorage.clear();

// sessionStorageの全データを削除
sessionStorage.clear();

このメソッドは、そのオリジンに保存されているすべてのデータを削除するため、使用には注意が必要です。

保存されているキーの取得

key()メソッドとlengthプロパティを使用して、保存されているすべてのキーを取得できます。

1
2
3
4
5
6
// ストレージ内のすべてのキーを取得
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

JSONを使ったオブジェクトの保存

なぜJSON変換が必要なのか

localStoragesessionStorageは、文字列のみを保存できます。オブジェクトや配列をそのまま保存しようとすると、[object Object]という文字列に変換されてしまいます。

1
2
3
const user = { name: "田中", age: 25 };
localStorage.setItem("user", user);
console.log(localStorage.getItem("user")); // "[object Object]"

この問題を解決するために、JSON形式に変換して保存します。

オブジェクトの保存と取得

JSON.stringify()でオブジェクトをJSON文字列に変換して保存し、JSON.parse()でJSON文字列をオブジェクトに復元します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// オブジェクトを保存
const user = {
  name: "田中",
  age: 25,
  email: "tanaka@example.com"
};
localStorage.setItem("user", JSON.stringify(user));

// オブジェクトを取得
const savedUser = JSON.parse(localStorage.getItem("user"));
console.log(savedUser.name); // "田中"
console.log(savedUser.age);  // 25

配列の保存と取得

配列も同様にJSON変換を使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 配列を保存
const todos = [
  { id: 1, text: "買い物", done: false },
  { id: 2, text: "掃除", done: true },
  { id: 3, text: "料理", done: false }
];
localStorage.setItem("todos", JSON.stringify(todos));

// 配列を取得
const savedTodos = JSON.parse(localStorage.getItem("todos"));
console.log(savedTodos.length); // 3
console.log(savedTodos[0].text); // "買い物"

安全なデータ取得のパターン

データが存在しない場合や、不正なJSON文字列が保存されている場合に備えて、エラーハンドリングを行うことをおすすめします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function getStoredData(key, defaultValue = null) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error("データの取得に失敗しました:", error);
    return defaultValue;
  }
}

// 使用例
const user = getStoredData("user", { name: "ゲスト" });
console.log(user.name); // 保存されていれば "田中"、なければ "ゲスト"

実践的な活用例

ダークモード設定の保存

ユーザーのテーマ設定をlocalStorageに保存して、次回訪問時にも維持する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// テーマ設定を保存
function setTheme(isDark) {
  localStorage.setItem("darkMode", JSON.stringify(isDark));
  document.body.classList.toggle("dark", isDark);
}

// ページ読み込み時に設定を復元
function loadTheme() {
  const isDark = JSON.parse(localStorage.getItem("darkMode")) || false;
  document.body.classList.toggle("dark", isDark);
  return isDark;
}

// 切り替えボタンの処理
const toggleButton = document.getElementById("themeToggle");
let isDarkMode = loadTheme();

toggleButton.addEventListener("click", () => {
  isDarkMode = !isDarkMode;
  setTheme(isDarkMode);
});

フォーム入力の自動保存

ページの再読み込みや意図しないタブの閉じ操作に備えて、フォームの入力内容をsessionStorageに自動保存する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const form = document.getElementById("contactForm");
const nameInput = document.getElementById("name");
const emailInput = document.getElementById("email");
const messageInput = document.getElementById("message");

// 保存されたデータを復元
function restoreFormData() {
  const savedData = sessionStorage.getItem("formData");
  if (savedData) {
    const data = JSON.parse(savedData);
    nameInput.value = data.name || "";
    emailInput.value = data.email || "";
    messageInput.value = data.message || "";
  }
}

// フォームデータを保存
function saveFormData() {
  const data = {
    name: nameInput.value,
    email: emailInput.value,
    message: messageInput.value
  };
  sessionStorage.setItem("formData", JSON.stringify(data));
}

// 入力時に自動保存
form.addEventListener("input", saveFormData);

// 送信成功時にデータをクリア
form.addEventListener("submit", (e) => {
  e.preventDefault();
  // 送信処理...
  sessionStorage.removeItem("formData");
});

// ページ読み込み時に復元
restoreFormData();

ショッピングカートの実装

商品情報をオブジェクトとしてlocalStorageに保存するショッピングカートの例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// カートの操作を管理するオブジェクト
const cart = {
  // カート内容を取得
  getItems() {
    const items = localStorage.getItem("cart");
    return items ? JSON.parse(items) : [];
  },

  // 商品を追加
  addItem(product) {
    const items = this.getItems();
    const existingItem = items.find(item => item.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      items.push({ ...product, quantity: 1 });
    }
    
    localStorage.setItem("cart", JSON.stringify(items));
    return items;
  },

  // 商品を削除
  removeItem(productId) {
    const items = this.getItems().filter(item => item.id !== productId);
    localStorage.setItem("cart", JSON.stringify(items));
    return items;
  },

  // カートをクリア
  clear() {
    localStorage.removeItem("cart");
  },

  // 合計金額を計算
  getTotal() {
    return this.getItems().reduce((total, item) => {
      return total + item.price * item.quantity;
    }, 0);
  }
};

// 使用例
cart.addItem({ id: 1, name: "商品A", price: 1000 });
cart.addItem({ id: 2, name: "商品B", price: 2000 });
console.log(cart.getItems());
console.log(`合計: ${cart.getTotal()}円`);

storageイベントによる変更検知

localStorageの変更は、同じオリジンの他のタブやウィンドウで検知できます。これにより、複数タブ間でのデータ同期が可能になります。

1
2
3
4
5
6
7
8
// storageイベントを監視
window.addEventListener("storage", (event) => {
  console.log("ストレージが変更されました");
  console.log("キー:", event.key);
  console.log("古い値:", event.oldValue);
  console.log("新しい値:", event.newValue);
  console.log("URL:", event.url);
});

このイベントは、変更を行ったタブ自身では発火せず、同一オリジンの他のタブでのみ発火します。

sequenceDiagram
    participant TabA as タブA
    participant Storage as localStorage
    participant TabB as タブB
    
    TabA->>Storage: setItem("key", "value")
    Storage-->>TabB: storageイベント発火
    TabB->>TabB: イベントハンドラで処理

セキュリティ上の注意点

XSS攻撃のリスク

localStoragesessionStorageに保存されたデータは、JavaScriptから常にアクセス可能です。そのため、XSS(クロスサイトスクリプティング)攻撃によって悪意のあるスクリプトが実行された場合、保存されたデータが窃取される可能性があります。

1
2
3
// 悪意のあるスクリプトがストレージにアクセスする例
const stolenData = localStorage.getItem("sensitiveData");
// 攻撃者のサーバーにデータを送信...

保存すべきでないデータ

セキュリティ上、以下のような機密情報はlocalStoragesessionStorageに保存すべきではありません。

  • パスワードやクレジットカード情報
  • アクセストークンやリフレッシュトークン
  • 個人を特定できる機密情報
  • APIキーやシークレット

安全な使用のためのガイドライン

ブラウザストレージを安全に使用するためのポイントをまとめます。

  1. 機密情報を保存しない: パスワードやトークンなどの機密情報は保存を避けます
  2. HTTPSを使用する: 通信経路での盗聴を防ぐため、必ずHTTPSを使用します
  3. 入力値の検証: ストレージから取得したデータを使用する際は、必ず検証を行います
  4. XSS対策の徹底: Content Security Policy(CSP)の設定や、入力値のサニタイズを行います
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// データ使用時の検証例
function getSafeUserData() {
  try {
    const userData = JSON.parse(localStorage.getItem("user"));
    
    // 期待する型かどうかを検証
    if (userData && typeof userData.name === "string") {
      return userData;
    }
    return null;
  } catch {
    // 不正なデータの場合は削除
    localStorage.removeItem("user");
    return null;
  }
}

Cookieとの使い分け

認証トークンなどの機密情報を扱う場合は、httpOnly属性とSecure属性を設定したCookieの使用を検討してください。httpOnly属性を設定したCookieはJavaScriptからアクセスできないため、XSS攻撃によるトークン窃取を防ぐことができます。

ストレージ容量の確認と制限

容量制限

ブラウザストレージには容量制限があり、一般的に5MB程度です。制限を超えるとエラーが発生します。

1
2
3
4
5
6
7
try {
  localStorage.setItem("largeData", "非常に大きなデータ...");
} catch (e) {
  if (e.name === "QuotaExceededError") {
    console.error("ストレージ容量を超えました");
  }
}

使用量の確認

現在の使用量を概算で確認する方法です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function getStorageSize() {
  let total = 0;
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += localStorage.getItem(key).length * 2; // UTF-16は2バイト/文字
    }
  }
  return (total / 1024).toFixed(2) + " KB";
}

console.log("使用量:", getStorageSize());

まとめ

localStoragesessionStorageは、ブラウザ側でデータを保存するためのシンプルで便利なAPIです。本記事で解説した内容をまとめます。

  • localStorageはブラウザを閉じてもデータが永続化され、sessionStorageはタブを閉じると消去される
  • setItemgetItemremoveItemclearの4つのメソッドでデータを操作する
  • オブジェクトや配列を保存する場合は、JSON.stringifyJSON.parseで変換する
  • storageイベントを使用して、他のタブでの変更を検知できる
  • セキュリティ上、機密情報の保存は避け、XSS対策を徹底する

これらの機能を適切に活用することで、ユーザー体験を向上させるWebアプリケーションを構築できます。

参考リンク