keepcalculator

06 Numbers and Powers

Type Fetch Response Data Safely in TypeScript

Card No. 06·55305 · Updated · Standard formula, shown below

This guide shows you how to handle the response from a fetch call in TypeScript without losing type safety or crashing at runtime. You’ll learn to validate the raw JSON, narrow unknown types, and avoid common pitfalls like trusting an unvalidated type assertion. By the end, you’ll have a reusable pattern for typed, safe data fetching.

What You’ll Need

  • TypeScript 4.4 or later (for unknown and satisfies support)
  • A basic understanding of generics and type guards
  • An environment where fetch is available (browser or Node.js 18+)

Step-by-Step

  1. Define your expected response type. Create an interface or type that describes the shape you expect from the API. For example, if you’re fetching a user, define interface User { id: number; name: string; email: string }.
  2. Create a type guard for that type. Write a function that checks if an unknown value matches your type. Use typeof, Array.isArray, and property checks. For example: function isUser(value: unknown): value is User { ... }. This is the core of safe typing—it narrows the type at runtime.
  3. Call fetch and parse the response. Use await fetch(url), then check response.ok to catch HTTP errors. Parse with await response.json(), which returns Promise<any>. Assign the result to a variable typed as unknown to force validation.
  4. Validate the parsed data with your type guard. Pass the unknown value to your guard. If it returns true, TypeScript narrows the type to your expected type. If false, throw an error or handle it gracefully.
  5. Wrap the whole thing in a try/catch. Handle network errors, JSON parse errors, and validation failures in one place. This prevents unhandled rejections and gives you a single error path.
  6. Create a reusable generic function. Abstract the pattern into a function like async function fetchTyped<T>(url: string, guard: (data: unknown) => data is T): Promise<T>. This way you only write the validation logic once and reuse it for different endpoints.
  7. Use the typed data. After the function returns, you can safely access properties with full autocomplete and type checking, because the data is guaranteed to be of type T.

Common Mistakes

  • Using as to cast the response. const data = await response.json() as User tells TypeScript to trust you, but if the API changes or returns an error object, you’ll get runtime errors that TypeScript can’t catch. Always validate.
  • Ignoring response.ok. A 404 or 500 still returns a response object. If you don’t check ok, you’ll try to parse an error page as JSON and likely get a syntax error or invalid data.
  • Assuming the JSON structure is flat. Nested objects need their own guards. Validate each level to avoid undefined property access.
  • Forgetting that arrays are objects. In a guard, check Array.isArray(value) before checking properties, otherwise you might treat an array as an object.
  • Not handling response.json() errors. If the response body isn’t valid JSON, json() throws. Wrap it in try/catch or check the content-type header first.

Tips

  • Use a validation library for complex schemas. Libraries like Zod or io-ts can generate type guards automatically from a schema, saving time and reducing errors. They also provide better error messages.
  • Keep guards small and composable. Write a guard for a single object, then combine them for larger structures. This makes testing easier.
  • Use unknown instead of any. unknown forces you to check the type before using it, while any disables type checking entirely.
  • Throw a custom error class. Create a ValidationError with details about what failed. This helps debugging in production.
  • Consider caching the parsed response. If you fetch the same data multiple times, store the validated result to avoid re-parsing.

Troubleshooting

  • TypeScript still shows any after validation. Make sure your guard returns a type predicate (value is T), not just a boolean. If it returns boolean, TypeScript won’t narrow the type.
  • Property access fails on the validated data. Your guard might be too permissive. Add more checks: verify the type of each property, not just that it exists.
  • Fetch throws a TypeError in Node.js. This often means the URL is malformed or the network is unreachable. Check the URL and ensure your environment has network access.
  • JSON parse error on a valid response. The server might be returning empty body or HTML. Check the Content-Type header and log the raw text with await response.text() to debug.
  • Validation fails for optional fields. If a property can be null or missing, update your guard to allow undefined. Use if ('property' in value) to check existence.

Frequently Asked Questions

  1. Why can’t I just use as to type the response? Because as is a compile-time assertion that doesn’t check the actual data. If the API returns unexpected data, your code will fail at runtime with no warning.
  2. Do I need a type guard for every API call? Not necessarily. If you trust the API and the cost of a runtime error is low, you might skip it. But for critical data, guards are worth the effort.
  3. What’s the difference between unknown and any? unknown is the safe counterpart of any. You must narrow it before using it; any bypasses type checking entirely.
  4. Can I use a class with a type guard? Yes, you can use instanceof in a guard for class instances. But for plain JSON data, interfaces are more common.
  5. How do I handle array responses? Create a guard for the element type, then check Array.isArray(value) && value.every(isElement).
  6. Is it okay to use a library like Zod? Absolutely. Libraries like Zod generate types from schemas, so you get both validation and type inference in one place. They’re especially useful for large projects.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →