typescript 54 lines · 9 steps

A type-safe wrapper around localStorage

A generic class maps a schema type onto the untyped Storage API so every key and value stays checked at compile time.

Explained by highlit
1type StorageSchema = Record<string, unknown>;
2 
3interface StorageOptions {
4 namespace?: string;
5 storage?: Storage;
6}
7 
8class TypedStorage<T extends StorageSchema> {
9 private readonly prefix: string;
10 private readonly store: Storage;
11 
12 constructor(options: StorageOptions = {}) {
13 this.prefix = options.namespace ? `${options.namespace}:` : "";
14 this.store = options.storage ?? window.localStorage;
15 }
16 
17 private keyFor(key: keyof T): string {
18 return `${this.prefix}${String(key)}`;
19 }
20 
21 get<K extends keyof T>(key: K): T[K] | null {
22 const raw = this.store.getItem(this.keyFor(key));
23 if (raw === null) return null;
24 try {
25 return JSON.parse(raw) as T[K];
26 } catch {
27 this.store.removeItem(this.keyFor(key));
28 return null;
29 }
30 }
31 
32 getOr<K extends keyof T>(key: K, fallback: T[K]): T[K] {
33 return this.get(key) ?? fallback;
34 }
35 
36 set<K extends keyof T>(key: K, value: T[K]): void {
37 this.store.setItem(this.keyFor(key), JSON.stringify(value));
38 }
39 
40 update<K extends keyof T>(key: K, updater: (current: T[K] | null) => T[K]): void {
41 this.set(key, updater(this.get(key)));
42 }
43 
44 remove(key: keyof T): void {
45 this.store.removeItem(this.keyFor(key));
46 }
47 
48 clear(): void {
49 for (let i = this.store.length - 1; i >= 0; i--) {
50 const k = this.store.key(i);
51 if (k && k.startsWith(this.prefix)) this.store.removeItem(k);
52 }
53 }
54}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A generic type parameter constrained to a schema lets one class enforce per-key value types across every method.
  2. 2Wrapping a stringly-typed browser API in JSON serialization plus try/catch turns fragile storage access into a safe, self-cleaning interface.
  3. 3A key prefix scopes reads and writes to a namespace so multiple stores can share the same underlying Storage without collisions.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A type-safe wrapper around localStorage — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code