Type your storage

3 min read

tl;dr - In this post I go through some advantages of adding typesafety to the storage API using a schema based approach, which boil down to:

  • A single source of truth of the storage structure
  • Type safe interaction with the storage
  • Less code duplication

Working with the Storage API - mainly its implementations localStorage and sessionStorage - has become an everyday task for developers since its introduction around 2010. It provides applications with persistent data storage without the need of relying on cookies and it is an important part of modern web apps. Just as the Storage API replaced cookies in many use cases, we are in the middle of yet another trend that seems unstoppable, that is, the shift from using JavaScript to TypeScript for web development. As the State of JS shows, about 78% of JS programmers already use TypeScript, and a good amount of the rest is interested in trying.

Why you should type your store

JavaScript was not made for creating large, complex apps the modern web requires. Therefore, TypeScript aims to address the subsequent problems that arise when developing large systems. One key feature to achieve this is the introduction of static types. Static types are great because it can help to catch errors before running code, thus making code easier to refactor, APIs easier to read and so forth. Unfortunately, when coming from a dynamically typed language like JS it is often very tempting to merely -and brutally- cast types until they fit. To avoid undermining the type system though, lines like the following should be avoided in most cases

const data = apiResponse as undefined as SomeDataType;

Type casts like this one often arise out of inexperience, convenience or are -even- simply unavoidable. However, there is one perfectly avoidable case I keep stumbling across - interactions with the localstorage. The straightforward way of storing and retrieving anything else than a string is

// setting an item
window.localStorage.setItem('my-key', JSON.stringify(data));

// retrieving an item
const soredData = JSON.parse(window.localStorage.getItem('my-key')) as SomeDataType;

As long as only a couple of things are stored this is not a problem. Nevertheless, the downsides of this approach, namely that there is no type check when adding an item to the store, that it is tedious to type, and the lack of knowledge of how the localStorage looks like, grow as the complexity of the storage grows. Despite this, fear not because I bring good news: we can do better!

How to type your store

By defining our storage structure with a schema similar to the the following

interface Schema {
  counter: number;
  message: string;
  user: {
    id: string;
    name: string;
    email: string;
    isAdmin: boolean;
  posts: string[];

We can achieve a more type safe way of interacting with the localStore. Instead of accessing the storage through the bare Storage API, we can now construct a wrapper which takes the schema as the underlying storage structure. The implementation details can be found in my previous post. It boils down to initializing a store once and enjoy a type safe storage access

const typedStorage = new TypedLocalStore<Schema>();

// only allows correct user type
typedStorage.setItem(user, user);

// storedUser is typed
Const storedUser = typedStorage.getItem(user);

The core advantages of this approach are

…which fits nicely into the typescript ecosystem. We have had good experience with this approach in context of several projects. For more insights and to save you some work we published a npm and deno package which incorporates this concept.