A Guide to Data Normalisation in Frontend JavaScript

Have you ever felt lost in the wild jungle of data structures, wishing for a magic wand to organise and simplify everything? Not to worry, we're about to embark on a journey into the realm of data normalisation - the superhero cape your frontend applications have been wishing for!

What exactly is data normalisation?

Data normalization in the context of frontend development usually refers to the process of organizing JSON data in a format that makes it easier to work with in state management tools like Redux. The goal is to make the data more predictable, easier to update, and more efficient to access.

How to Normalize Data

  1. Break Down Data into Entities: Separate your data into entities if it has a structure where objects contain nested objects. Each type of object becomes its own "table" similar to databases.

  2. Use IDs for Reference: Instead of nesting objects, use IDs to reference other objects. This prevents data duplication and helps in managing updates cleanly.

  3. Store Data in Objects, Not Arrays: Use objects keyed by IDs for storing entities. This makes it fast to find an entity by its ID.

Example

Suppose you have an array of blog posts where each post includes the author's details and comments. Here’s how you can normalize this data:

// Original data
const blogPosts = [
  {
    id: 1,
    title: "Hello World",
    author: { id: 1, name: "John Doe" },
    comments: [
      { id: 1, text: "Great post!" },
      { id: 2, text: "Thanks for the info." }
    ]
  },
  {
    id: 2,
    title: "Data Normalization",
    author: { id: 2, name: "Jane Smith" },
    comments: [
      { id: 3, text: "Very helpful post." }
    ]
  }
];

// Normalized data
const normalizedData = {
  posts: {
    1: { id: 1, title: "Hello World", authorId: 1, comments: [1, 2] },
    2: { id: 2, title: "Data Normalization", authorId: 2, comments: [3] }
  },
  authors: {
    1: { id: 1, name: "John Doe" },
    2: { id: 2, name: "Jane Smith" }
  },
  comments: {
    1: { id: 1, text: "Great post!" },
    2: { id: 2, text: "Thanks for the info." },
    3: { id: 3, text: "Very helpful post." }
  }
};

// Accessing a post, its author, and comments
const post = normalizedData.posts[1];
const author = normalizedData.authors[post.authorId];
const comments = post.comments.map(commentId => normalizedData.comments[commentId]);

console.log(post, author, comments);

Benefits of Normalization

  • Consistency: Data is stored in one place, so updates and deletions are straightforward and consistent.

  • Performance: Retrieving an item by ID from an object is faster than searching through an array.

  • Maintainability: It's easier to understand and navigate a normalized state structure, especially as the application grows.

This approach fits well with libraries like Redux but can also be useful in any context where you manage a complex data state on the client side.

Let's learn how can we normalize a data, Assume you have an API response that includes user and post data. Let's sprinkle some normalisation magic instead of a tangled mess!

// Original API Response
const apiResponse = {
  users: [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' },
  ],
  posts: [
    { id: 101, userId: 1, title: 'Post 1' },
    { id: 102, userId: 2, title: 'Post 2' },
  ],
};

function normalizeApiResponse(data) {
  // Normalize users
  const users = data.users.reduce((acc, user) => {
    acc[user.id] = user;
    return acc;
  }, {});

  // Normalize posts
  const posts = data.posts.reduce((acc, post) => {
    acc[post.id] = { ...post, userId: post.userId };
    return acc;
  }, {});

  // Combine normalized data into a single object
  return { users, posts };
}

// Example usage:
const normalizedData = normalizeApiResponse(apiResponse);
console.log(normalizedData);

// Normalized Data
const normalizedData = {
  users: {
    1: { id: 1, name: 'John Doe' },
    2: { id: 2, name: 'Jane Smith' },
  },
  posts: {
    101: { id: 101, userId: 1, title: 'Post 1' },
    102: { id: 102, userId: 2, title: 'Post 2' },
  },
};

Let's break down the provided code and then see how you might use this normalized data in a practical scenario.

In the original API response, we have data about users and their associated posts. The users array contains objects with id and name properties, and the posts array includes objects with id, userId, and title properties. It's a common structure you might receive when fetching data from an API.

  • Users Normalization: We use reduce on the users array. The accumulator (acc) starts as an empty object. For each user in the array, we add a new property to the object using the user's ID as the key. This property's value is the user object itself.

  • Posts Normalization: Similarly, we use reduce on the posts array. Each post is added to the accumulator object, with the post ID as the key. The post object is stored as the value, ensuring it includes the userId to maintain the relationship between posts and users.

Now, the normalized data structure is created. Instead of having arrays, it uses objects where each item is keyed by its id. This structure is more efficient for certain operations, especially when you need to look up or update specific items quickly.

This technique helps a lot in handling huge datasets gracefully, it's easy to use and gives lot of flexibility while working with data in frontend applications

I'm still learning, so please let me know in the comments if there is anything I missed or if you think something is wrong, Thanks for reading.