Ensuring type safety in LocalStorage API with Typescript
Tags: typescript, tutorial, webdev, frontend
Published: 2023-03-26 10:01:04

> Also available on my [blog](https://nikolasbarwicki.com/articles/ensuring-type-safety-in-localstorage-api-with-typescript)

## Introduction to Typescript and its Advantages

[Typescript](https://www.typescriptlang.org/) is a superset of JavaScript that provides optional **static type checking** and other features to improve code quality and maintainability. By adding type annotations to variables, functions, and other parts of the code, Typescript can catch errors at compile-time and help developers write **more robust and reliable applications**.

In this article, we will discuss how Typescript can help ensure type safety in applications that use local storage, user input, network requests, and other parts of the app logic that **are not type safe by default**. We will also provide tips and examples of how to use Typescript to improve the type safety of your code and prevent runtime errors.

## Why some app logic aren't type safe

Despite the benefits of using Typescript, there are still parts of an application that **are not type safe by default**. This includes user input, local storage, network requests, and other parts of the app logic that can be affected by external factors.

For example, **data stored in local storage can be modified by the user or by other scripts, making it difficult to guarantee its type safety**. Similarly, network requests can return unexpected data or errors, making it important to handle them appropriately.

Ensuring type safety in these parts of the application is crucial for maintaining the reliability and security of the application. By using Typescript, developers can minimize the risk of runtime errors and make it easier to maintain the application over time.

## Making Typescript happy and improving type safety

To improve the type safety of an application, there are several techniques that developers can use when working with TypeScript.

- First, developers can use **type annotations** to specify the types of variables, function parameters, and return values. Type annotations help TypeScript catch errors at compile-time and provide better documentation for the code.

- Second, TypeScript's type inference feature **can automatically infer the type of a variable based on its value**. This can help reduce the amount of code that needs to be annotated and improve code readability.

- Finally, developers can take advantage of **TypeScript's built-in features**, such as interfaces, classes, and enums, to create reusable and maintainable code.

By using these techniques, developers can **"make TypeScript happy"** and improve the type safety of their application, making it more reliable and easier to maintain over time.

## Custom type guard for LocalStorage API

**[LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) is a browser API that allows applications to store data locally on the user's device**. While convenient, LocalStorage is not type safe by default and can store any data type as a string.

To ensure type safety when reading data from LocalStorage, developers can create a [custom type guard](https://nikolasbarwicki.com/articles/typescript-type-guards-how-to-use-them) that checks the type of the stored data and returns a typed value. Here's an example of how to create a type guard for a string array:

```typescript
function isStringArray(value: any): value is string[] {
  return Array.isArray(value) && value.every((item) => typeof item === 'string')
}
```

This function takes a value of type `any` and returns a `boolean` indicating whether the value is a string array. To use this type guard when reading from LocalStorage, we can pass the stored value to the `isStringArray` function and use the result to either assign the typed value to a variable or handle a runtime error.

Here's an example of how to use the type guard to read a string array from LocalStorage:

```typescript
const storedValue = localStorage.getItem('myArray')

if (storedValue !== null) {
  const parsedValue = JSON.parse(storedValue)

  if (isStringArray(parsedValue)) {
    // Use the typed value
    const myArray: string[] = parsedValue
  } else {
    // Handle the error
    console.error('Invalid data type in LocalStorage')
  }
}
```

In this example, we first retrieve the stored value from LocalStorage and parse it as JSON. We then use the type guard `isStringArray` **to check whether the parsed value is a string array**. If it is, we can assign it to a typed variable and use it in the application. **If it's not, we handle the error by logging a message to the console**.

Using a custom type guard for LocalStorage data can improve the type safety of an application and prevent runtime errors caused by unexpected data types. By checking the type of the stored data **before using it in the application**, we can catch errors early and provide a **better user experience**.

### An alternative way using Zod

```typescript
import { z } from 'zod';

const stringArraySchema = z.array(z.string());

const storedValue = localStorage.getItem('myArray');

if (storedValue !== null) {
  const parsedValue = JSON.parse(storedValue);

  try {
    const myArray = stringArraySchema.parse(parsedValue);

    // Use the typed value
  } catch (error) {
    // Handle the error
    console.error('Invalid data type in LocalStorage', error);
  }
}
```

In this example, we first define a [Zod](https://github.com/colinhacks/zod) schema for a string array using the `z.array` and `z.string` methods. We then retrieve the stored value from LocalStorage and parse it as JSON. We use the `stringArraySchema.parse` method to validate and type the parsed value. If the value is valid, we can assign it to a typed variable and use it in the application. If it's not valid, we handle the error by logging a message to the console.

By using Zod, we can **achieve the same result** as the custom type guard example while also benefiting from the additional features of a validation library, such as automatic type inference using `z.infer`.

## Conclusion

Type safety is an **essential aspect of developing robust and maintainable applications**. While TypeScript provides powerful type checking features, certain parts of an application, such as LocalStorage, user input, network requests, or file system operations, may not be type safe by default. To ensure type safety in these cases, developers can use techniques like **custom type guards** or **validation libraries** like Zod.

Remember that **type safety is just one aspect** of building robust applications. It's also essential to follow best practices like code organization, testing, and documentation. By combining these techniques, developers can create applications that are reliable, maintainable, and enjoyable to use.

## Bonus Tips

While creating custom type guards and using validation libraries like Zod can improve type safety in an application, there are a few additional tips that developers can follow to ensure maximum type safety:

1. Use `strictNullChecks`: TypeScript's `strictNullChecks` [compiler option](https://www.typescriptlang.org/tsconfig#strictNullChecks) can prevent many common runtime errors by disallowing the use of null or undefined values unless explicitly specified. Enabling this option can help catch errors caused by missing data or uninitialized variables.

2. **Use interfaces and types**: Interfaces and types can help define clear and consistent data structures in an application. By using interfaces or types for objects, developers can ensure that properties have the correct types and avoid runtime errors caused by misspelled property names or incorrect types.

3. **Use generics**: Generics can help create reusable and flexible functions that work with multiple types. By using generics, developers can ensure that functions work with the correct data types and avoid type casting errors.

4. **Use third-party libraries**: Many third-party libraries provide type definitions that can improve type safety in an application. By using libraries with type definitions, developers can avoid manually defining types and ensure compatibility with other libraries.

By following these tips, developers can create more robust and maintainable applications with maximum type safety. Remember that type safety is just one aspect of building reliable applications, and it's essential to follow best practices in all aspects of development.