Taming TypeScript

The TypeScript Tutorial for The Hater

The Ultimate Test: Typing a Real fetch Request

The Final Challenge

Welcome to the capstone lesson for this module! We have learned how to handle unknown data, the dangers of the as command, and the safety of the is type guard.

Now, it's time to put it all together and solve the most common problem in front-end development: safely handling data from an API.

The Goal: Fetching User Data

We're going to fetch a user from the popular JSONPlaceholder test API. Our goal is to create a system that safely fetches this data, validates it, and only processes it if it matches the User shape we expect.

Step 1: Define the Type and the Type Guard

First, we define our User type and the isUser type guard we built in the previous lesson. This function is the heart of our safety model.

type User = {
  id: number;
  name: string;
  email: string;
};

function isUser(data: unknown): data is User {
  if (typeof data !== 'object' || data === null) {
    return false;
  }

  const hasId = 'id' in data;
  const hasName = 'name' in data;
  const hasEmail = 'email' in data;

  return hasId && hasName && hasEmail;
}

Step 2: Plan for Success and Failure

Next, we'll create two simple functions to handle the two possible outcomes of our investigation: one for when the data is a valid User, and one for when it's not.

function processValidUser(user: User) {
  console.log(`✅ Success! Welcome, ${user.name}.`);
}

function handleInvalidData(data: unknown) {
  console.error('❌ Error: The fetched data was not a valid User.', data);
}

Step 3: The fetch Chain

Finally, we'll use the standard fetch API. The key is what happens inside the final .then() block. We receive the unknown data and immediately hand it over to our isUser investigator.

Based on the true or false result, we can then call the appropriate handler function with full type safety.

Interactive Editor
Loading...
Show Full Code
type User = {
  id: number;
  name: string;
  email: string;
};

function isUser(data: unknown): data is User {
  if (typeof data !== 'object' || data === null) {
    return false;
  }
  const hasId = 'id' in data;
  const hasName = 'name' in data;
  const hasEmail = 'email' in data;
  return hasId && hasName && hasEmail;
}

function processValidUser(user: User) {
  console.log(`✅ Success! Welcome, ${user.name}.`);
}

function handleInvalidData(data: unknown) {
  console.error('❌ Error: The fetched data was not a valid User.', data);
}

fetch('https://jsonplaceholder.typicode.com/users/1')
  .then((response) => response.json())
  .then((unknownData) => {
    if (isUser(unknownData)) {
      processValidUser(unknownData);
    } else {
      handleInvalidData(unknownData);
    }
  });
💡 Optional: A More Production-Ready Guard

Our isUser guard is great for learning, but a real-world version should also check that the values have the correct types.

Here is a more robust version. It uses some advanced TypeScript like Record that we will cover in detail in the "Generics" module. You don't need to understand how it works yet, just recognize the pattern of checking both the key's presence and the value's type.

function isUserRobust(data: unknown): data is User {
  if (typeof data !== 'object' || data === null) return false;

  // This is a safe way to check properties on an unknown object.
  // We will learn how this works in the Generics module.
  const maybeUser = data as Record<string, unknown>;

  return (
    'id' in maybeUser && typeof maybeUser.id === 'number' &&
    'name' in maybeUser && typeof maybeUser.name === 'string' &&
    'email' in maybeUser && typeof maybeUser.email === 'string'
  );
}

Conclusion: From Validation to Production

Congratulations! You have now built a complete, robust, and type-safe system for handling data from an external API.

You are right to ask if developers write this code manually every day. In many professional projects, developers use libraries like Zod to automate these checks. Zod is incredibly popular because it is a powerful tool for implementing the exact define -> guard -> validate principle we learned here.

So, while you may not write this specific code daily, you will rely on this principle constantly. Understanding what tools like Zod do under the hood is the difference between using a tool and mastering it. This knowledge is fundamental to writing truly robust TypeScript.

In the next module, we'll learn about Generics, which will allow us to understand and build even more powerful validation tools.