I used to manually write TypeScript interfaces for every API response. After the third time I mistyped a property name, I automated it. Here's why you should too.
The typo that cost me 3 hours
Last month I spent three hours debugging why my React component wasn't rendering data correctly. The API was returning data. The network tab showed everything was fine. But my UI was blank.
Turns out I had typed `user.firstName` when the API returned `user.first_name`. Classic snake_case vs camelCase mismatch. TypeScript didn't catch it because I had manually typed the interface and gotten lazy with the property names.
That was the moment I decided to stop writing TypeScript interfaces by hand. The thing is, the JSON structure already exists. Why am I recreating it manually and introducing errors?
Why manual typing is error-prone
When you manually write TypeScript interfaces for API responses, you're essentially duplicating information that already exists in a structured format. And every time you duplicate information, you create opportunities for drift.
I've seen teams where the API documentation says one thing, the backend returns another, and the frontend types describe a third thing. Nobody knows which one is correct anymore.
The worst part? TypeScript gives you false confidence. You think you're type-safe, but you're actually just type-wrong. The compiler can't catch runtime mismatches between your types and actual data.
- Typos in property names (firstName vs first_name)
- Wrong types (string vs number for IDs)
- Missing optional fields that became required
- Nested objects with incorrect structure
- Arrays typed as single objects or vice versa
- Outdated types after API changes
Interface vs type: does it matter?
I get asked this a lot: should I use interface or type for my API responses? Honestly, for generated types, it rarely matters. But here's my take.
I prefer interfaces for object shapes because they're extendable. If I need to add computed properties or extend from a base type later, interfaces make that easier. They also show up better in IDE hover information.
Types are better for unions, intersections, and utility types. If your API returns different shapes based on a discriminator, a type union is the way to go.
But here's the real answer: pick one and be consistent. The benefits of either over the other are marginal. What matters is that you're generating these types from actual data, not writing them by hand.
The API response workflow
Here's how I handle API responses now. First, I make the actual API call and look at the real response. Not the documentation, not what I think it should be—the actual JSON that comes back.
Then I paste that JSON into a type generator. I review the generated types, maybe adjust some optional properties, and export them to my codebase. Takes about 30 seconds.
The key insight is that the JSON response is the source of truth. Everything else—documentation, types, tests—should derive from it, not the other way around.
- Make the actual API call (Postman, DevTools, cURL)
- Copy the real JSON response
- Paste into JSON to TypeScript converter
- Review generated types for accuracy
- Adjust optional vs required as needed
- Export to your codebase
Handling nested objects and arrays
Real API responses are messy. You've got nested objects, arrays of objects, nullable fields, and sometimes arrays of arrays. Manual typing for these structures is where most errors happen.
A good type generator handles all of this automatically. It detects when a property is an array, creates separate interfaces for nested objects, and preserves the structure accurately.
I've learned to pay special attention to arrays. Sometimes an API returns a single object when there's one item and an array when there are multiple. That's a union type situation, and catching it early saves debugging time later.
Optional fields and null values
One thing automatic generators can't always get right is optional vs required fields. If your JSON sample has all fields populated, the generator might mark everything as required when some fields are actually optional.
My approach: generate the types first, then review them against the API documentation or schema. Mark fields as optional that might not always be present. Add null to union types where the API can return null.
This is where human judgment still matters. The generator gives you a starting point; you refine it based on your knowledge of the API.
- Review generated types against API docs
- Mark optional fields with ?
- Add | null for nullable fields
- Consider | undefined for truly optional properties
- Use Partial<T> when all fields are optional
- Create separate types for create vs read operations
Type safety beyond generation
Generating types is just the first step. To get real type safety, you need to validate data at runtime too. TypeScript types disappear at runtime—they can't catch bad data coming from an API.
I use Zod schemas alongside TypeScript types for critical data. The schema validates at runtime, and you can infer the TypeScript type from the schema. Single source of truth, runtime validation, and compile-time type checking.
For less critical data, I at least add type assertions at the API boundary. A simple function that asserts the shape of incoming data helps catch issues early rather than deep in your component tree.
FAQ
Should I use interface or type?
I prefer interfaces for objects because they're extendable and show up nicely in IDE tooltips. But honestly, for generated types from JSON, it rarely matters. Pick one and be consistent across your codebase.
What about optional fields?
Generators can't always know if a field is optional. I generate types first, then review against API docs and mark optional fields with ?. It takes a minute but saves debugging time later.
Should I regenerate types when the API changes?
Yes, absolutely. I keep generated types in a separate file and regenerate when the API version changes. Some teams automate this in CI by generating types from OpenAPI specs.
How do I handle API responses that vary?
Use union types. If an endpoint returns different shapes based on status or type discriminator, generate types for each variant and create a union. TypeScript's narrowing will help you handle each case.
Is runtime validation necessary?
For critical data, yes. TypeScript types don't exist at runtime. I use Zod for important API responses—it validates data and gives you TypeScript types from the same schema. Best of both worlds.