RizTech Academy logo
RizTech Academy
The DOMLesson 6 of 720 min

localStorage, and why it is not a database

Everything you have built so far disappears on reload. localStorage is the simplest fix — five methods, no setup — and it is also the most over-used tool in front-end development. Both halves of that matter.

The whole API

localStorage.setItem('customer', 'Priya');
console.log(localStorage.getItem('customer'));

localStorage.removeItem('customer');
console.log(localStorage.getItem('customer'));

console.log(localStorage.length);
localStorage.clear();
Priya
null
0

That is all of it: setItem, getItem, removeItem, clear, length. It survives reloads, tab closes and restarts, and it is per-origin — one site cannot read another's.

A missing key gives null, not undefined and not an error.

Everything is a string

This is the rule the whole lesson hangs off.

localStorage.setItem('plates', 3);
console.log(typeof localStorage.getItem('plates'));

localStorage.setItem('order', { customer: 'Priya' });
console.log(localStorage.getItem('order'));
string
[object Object]

The number came back as '3'. The object was converted with String() and is now the useless text [object Object] — no error, no warning, and the data is gone.

Use JSON, from module 4:

const order = { customer: 'Priya', plates: 3 };

localStorage.setItem('order', JSON.stringify(order));
const back = JSON.parse(localStorage.getItem('order'));

console.log(back.plates + 1);
4

And everything module 4 said about JSON applies in full: dates come back as strings, undefined properties vanish, Map and Set become {}. A Date saved here and loaded back is text until you rebuild it with new Date(...).

One convenient accident: JSON.parse(null) returns null rather than throwing, so reading a key that was never set is safe:

console.log(JSON.parse(localStorage.getItem('never-set')));
null

That is not true of JSON.parse(undefined), which throws — the difference matters, and it is why getItem returning null is helpful here.

It can throw, and it can be empty

Two failures that only appear on someone else's device.

Storage can be full. The limit is around 5MB per origin. Exceeding it throws a QuotaExceededError.

Storage can be unavailable. Some privacy modes and blocked-cookie settings make even reading throw.

So a wrapper is worth writing once:

export function save(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch {
    return false;
  }
}

export function load(key, fallback = null) {
  try {
    const text = localStorage.getItem(key);
    return text === null ? fallback : JSON.parse(text);
  } catch {
    return fallback;
  }
}

That catch covers both the quota error and corrupted text — somebody's browser extension, an interrupted write, or an older version of your own code that stored a different shape. Never assume what you get back is what you wrote; it may have been written by a version of your site from six months ago.

The page must still work when load returns the fallback. Storage is an optimisation, not a foundation.

localStorage and sessionStorage

Identical APIs, different lifetimes:

localStorage sessionStorage
Survives reload Yes Yes
Survives tab close Yes No
Shared between tabs Yes No — per tab
Cleared by Your code, or the user Closing the tab

sessionStorage is right for a multi-step form that should not reappear tomorrow.

What it is not for

The important half of the lesson.

It is not a database. No querying, no indexes, no relationships. Everything is a string keyed by another string. Storing 500 orders and filtering them means loading and parsing all 500 every time.

It is not secure. Any script on your page can read all of it, and so can anyone who opens devtools. Never put a password, a payment detail or an authentication token in it. A single cross-site scripting hole — the innerHTML risk from the selecting lesson — turns readable storage into a stolen session. Tokens belong in httpOnly cookies, which JavaScript cannot read at all.

It is not shared. It lives in one browser on one device. Your user's phone and laptop have different storage, and clearing site data wipes it. Anything that must follow a person belongs on a server.

It is synchronous, which means it blocks. Reading a large JSON blob on every keystroke will make a mid-range phone stutter. Read once into a variable, work with that, and write back when something changes.

So: use it for preferences, drafts, a cached list, "you were here last time". Not for anything you would be upset to lose, and not for anything secret.

When you need more — large data, structured queries, offline sync — the answer is IndexedDB or a real backend, and that is a different course.

A pattern that works

Keep state in a variable, persist as a side effect:

let orders = load('orders', []);

function addOrder(order) {
  orders = [...orders, order];
  save('orders', orders);
  render();
}

The variable is the truth; storage is a copy. Reading from localStorage every time you need the list would be slow and would scatter parsing through your code. Module 4's non-mutating update, and the next lesson builds on exactly this shape.

Check your work

getItem on a missing key returns null.

Everything is a string. setItem('plates', 3) comes back as '3', and storing an object directly gives '[object Object]' with no error — the data is lost.

JSON.stringify on the way in and JSON.parse on the way out, with all of module 4's caveats: dates become strings, undefined properties vanish, Map becomes {}.

JSON.parse(null) returns null rather than throwing, so parsing a never-set key is safe. JSON.parse(undefined) does throw.

setItem can throw — QuotaExceededError at about 5MB, or a privacy setting blocking access. Wrap reads and writes, and make the page work without them.

sessionStorage has the same API but dies with the tab and is not shared between tabs.

It is not a database, not secure, not shared between devices, and synchronous. Never store tokens or passwords; any XSS hole reads all of it.

Keep state in a variable and treat storage as a copy.

Practice

  1. Store a name, read it back, remove it, and read it again to see the null.
  2. Store a number and prove it comes back as a string. Then add 1 to it and explain the result.
  3. Store an object directly with setItem and look at what comes back. Confirm nothing errored.
  4. Do it properly with JSON.stringify and JSON.parse, and do arithmetic on a number from the parsed object.
  5. Store an object containing a Date, read it back, and call getFullYear() on it. Read the error, then rebuild the date.
  6. JSON.parse a key that was never set and confirm you get null rather than an exception.
  7. Write the save and load wrapper. Test load on a missing key and on a key whose value you have corrupted by hand in the Application panel.
  8. Fill it up. Write a loop storing a large string repeatedly until it throws, and catch the error. Note which error you got and roughly how much fitted.
  9. Put something in sessionStorage and something in localStorage. Reload — both survive. Close the tab and reopen — only one does.
  10. Open the Application panel in devtools and look at your own stored data. Edit a value by hand and reload the page. That is what "not secure" means, seen with your own eyes.
  11. Harder. Take the order list from the delegation lesson and make it survive a reload: state in a variable, saved on every change, loaded at start with a fallback to an empty list. Then deliberately corrupt the stored value in devtools and confirm the page still loads rather than showing a blank screen. Finally, write a comment naming one thing in that list that should not be in localStorage if this were a real application.

Next: putting the whole module together — a to-do list with no framework.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship