Learn JavaScript - Web Storage, Cookies, and Client-Side State
Episode 18 of 23

Learn JavaScript - Web Storage, Cookies, and Client-Side State

This episode covers storing data on the browser side: localStorage for persistent data, sessionStorage for per-tab data, and cookies for data sent to the server. You learn JSON serialization, safe state patterns, and when to use each mechanism.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

When a user closes a tab and opens it again, an application usually loses all context. Web Storage, cookies, and other mechanisms give you a way to store state on the browser side — so preferences, shopping carts, and settings survive into the next visit.

Episode 18 covers the three main client-side storage mechanisms: localStorage, which persists until manually deleted; sessionStorage, which lives for one tab; and cookies, which are automatically sent to the server with every request. You'll also learn safe patterns: JSON serialization, handling corrupt data, and limiting what's allowed to be stored. The key concept: these three mechanisms serve different needs — choosing the wrong one, for example storing a secret token in localStorage, is a serious security mistake.

localStorage: Persistent Storage

Storing and Reading Strings

localStorage stores string key-value pairs and survives even when the browser is closed:

JSBasic localStorage
localStorage.setItem("tema", "gelap");
localStorage.setItem("kota", "Jakarta");
 
console.log(localStorage.getItem("tema"));
console.log(localStorage.getItem("tidak-ada"));
localStorage.removeItem("tema");

localStorage.setItem("tema", "gelap") stores a value, getItem reads it, and removeItem deletes it. getItem for a non-existent key returns null. All values are stored as strings — the number 42 will come back as "42". Capacity is about 5 MB per origin.

Storing Objects with JSON

Because only strings can be stored, objects must be serialized with JSON.stringify and parsed back with JSON.parse:

JSStoring an object in localStorage
const preferensi = {
  tema: "gelap",
  ukuranFont: 16,
  bahasa: "id",
};
 
localStorage.setItem("preferensi", JSON.stringify(preferensi));
 
const tersimpan = localStorage.getItem("preferensi");
const objek = JSON.parse(tersimpan);
 
console.log(objek.tema);
console.log(objek.ukuranFont);

JSON.stringify(preferensi) converts the object into a JSON string before storing, and JSON.parse turns it back into an object. The setItem with stringify and getItem with parse pattern is the standard for object-based state in localStorage.

Handling Corrupt Data

JSON.parse throws an error if the string isn't valid JSON — for example because of an older app version. Always wrap it in try...catch:

JSSafe parsing
function bacaPreferensi() {
  const mentah = localStorage.getItem("preferensi");
  if (mentah === null) return {};
 
  try {
    return JSON.parse(mentah);
  } catch {
    return {};
  }
}
 
const preferensi = bacaPreferensi();
console.log(preferensi.tema ?? "default");

try {...} catch returns an empty object if the data is corrupt, so the application doesn't crash when reading old state. The if (mentah === null) handles keys that never existed. This safe parse pattern is mandatory for applications that store state and may change formats between versions.

sessionStorage: Per-Tab Data

How It Differs from localStorage

sessionStorage has an API identical to localStorage, but its scope differs: data lives while the tab is open and disappears when the tab closes. Each tab has its own sessionStorage:

JSPer-tab sessionStorage
sessionStorage.setItem("posisiScroll", "850");
sessionStorage.setItem("keranjang", JSON.stringify([1, 2, 3]));
 
console.log(sessionStorage.getItem("posisiScroll"));
 
const keranjang = JSON.parse(sessionStorage.getItem("keranjang"));
console.log(keranjang.length);

sessionStorage.setItem("posisiScroll", "850") stores transient state for the active tab — fitting for scroll positions restored on back-navigation, form drafts, or multi-step wizard state that isn't saved yet. If the data must survive across visits, use localStorage; if it's enough for the active tab, sessionStorage is safer because it's cleaned up automatically.

Cookies: Data Sent to the Server

Working with document.cookie

Unlike Web Storage, cookies are automatically sent to the server on every HTTP request. Access them from JavaScript via document.cookie:

JSWriting and reading cookies
document.cookie = "bahasa=id; max-age=86400; path=/";
document.cookie = "sesi=abc123; path=/; HttpOnly";
 
console.log(document.cookie);

document.cookie = "bahasa=id; max-age=86400; path=/" sets a cookie valid for one day. The path=/ attribute makes the cookie apply to the whole site. HttpOnly marks a cookie unreadable by JavaScript — which is why an HttpOnly session cookie doesn't appear in the document.cookie output.

Limits and Security Attributes

Cookies have important rules you must understand:

  • Small size: about 4 KB per cookie, and the total is limited.
  • HttpOnly: the cookie can't be read by JavaScript — safer for tokens.
  • Secure: the cookie is only sent over HTTPS.
  • SameSite: controls when the cookie is sent to other sites.
JSA cookie with security attributes
document.cookie =
  "token=abc123; max-age=3600; path=/; Secure; SameSite=Strict";

Secure; SameSite=Strict restricts the cookie to HTTPS connections and prevents it from being sent from other sites — a basic defense against CSRF attacks. These cookie security attributes are a defense line that shouldn't be loosened without strong reason.

Choosing the Right Mechanism

A Decision Guide

Three mechanisms serve three different needs. The choice in short:

  • localStorage: persistent client data — preferences, content caches.
  • sessionStorage: temporary per-tab data — drafts, positions.
  • Cookies: data that must be sent to the server with every request — session tokens.

The most important security rule: don't store access tokens in localStorage. localStorage can be read by any script running on the page, and exposing it to an XSS attack means handing over that token. Tokens should live in an HttpOnly cookie that JavaScript can't reach.

A Simple State Pattern

A recommended practice: write small functions that hide the serialization details:

JSA safe state pattern
const state = {
  simpan(nama, nilai) {
    localStorage.setItem(nama, JSON.stringify(nilai));
  },
  baca(nama) {
    const mentah = localStorage.getItem(nama);
    if (mentah === null) return null;
    try {
      return JSON.parse(mentah);
    } catch {
      return null;
    }
  },
  hapus(nama) {
    localStorage.removeItem(nama);
  },
};
 
state.simpan("pengguna", { nama: "Arman" });
console.log(state.baca("pengguna"));

state.simpan(nama, nilai) and state.baca(nama) hide stringify and parse plus error handling in one place. All callers just use state without knowing the storage details. Patterns like this keep things consistent and make migration easier if the storage mechanism changes.

Wrap-Up

Episode 18 equipped you with client-side state storage: localStorage for persistent data with JSON serialization, sessionStorage for temporary per-tab data, and cookies for data sent to the server with security attributes. You also learned safe, centralized storage patterns.

Key takeaways:

  • localStorage persists until deleted; sessionStorage disappears when the tab closes.
  • Stored values are always strings — serialize with JSON.
  • Wrap JSON.parse in try...catch for data that could be corrupt.
  • Cookies are sent to the server automatically; Web Storage is not.
  • Don't store tokens in localStorage — use an HttpOnly cookie.
  • Centralize storage logic into a single state module.

In the next episode 19 we'll cover debugging and console tools — finding and fixing bugs with the Console API, breakpoints, and DevTools. You learn to read errors and stack traces, use debugger, and analyze performance with tools already in the browser.

Learn JavaScript - Web Storage, Cookies, and Client-Side State | Learn JavaScript