{ Simple Frontend }

Type safe data fetching for your frontend applications

Learn how to generate TypeScript types from your API specification to speed up development against your backend API and catch errors at compile time.

Jeremy Colin Jeremy Colin
Apr 24, 2025 - 4 min read
#Frontend

The case for type safe data fetching

Uncaught TypeError: Cannot read properties of undefined (reading 'id') is probably the most common error you’ve encountered in your frontend projects. You spend hours debugging to eventually discover a subtle typo in one of your query parameters or an expected property is missing from your API response.

Browser console error: Uncaught TypeError: Cannot read properties of undefined (reading 'id')

When you are writing frontend code to fetch data from an API, you not only want autocomplete but you also want the guarantee that it is 100% compatible with your runtime API, not only with the API specification which was published at the beginning of the project.

This is not only about preventing bugs but increasing your confidence as a developer. You will:

  1. Eliminate guesswork: you want to know as soon as possible when you frontend and backend code are getting out of sync.

  2. Boost your productivity: you do want to write API types by hand and have a manual process to keep them updated.

  3. Ship faster and safer: with API contracts validated at compile time, you catch issues in development you can start to rely less on manual testing.

If you are already working with an API that publishes an OpenAPI schema, you can jump to Generate TypeScript types from your API specification.

Publish an OpenAPI schema for your APIs

API specifications allow both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.

OpenAPI logo

The OpenAPI specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs. It does not require rewriting existing APIs, only that the service describe its capabilities in the structure of the OpenAPI Specification. When working with an API, it is a reasonable ask to demand that it is documented following such a spec and there are a multitude of Open Source tools helping you do that, for example:

Generate TypeScript types from your API specification

Now that you have an OpenAPI Specification for your API, you can generate TypeScript types for type-safe data fetching. Again here there are multiple solutions available but I can recommend OpenAPI TypeScript as it does the job and is well documented.

Think of it as a custom SDK library for your API, generated from the OpenAPI specification.

Install OpenAPI TypeScript as a dev dependency:

Terminal window
pnpm install --save-dev openapi-ts

Then you can generate the types from your OpenAPI specification with a script in your package.json:

"scripts": {
"generate:types": "openapi-typescript https://demo-type-safe-apis-production.up.railway.app/docs/json --output ./src/api-types.d.ts"
}

Now you can replace the URL with the URL of your API OpenAPI specification and run:

Terminal window
pnpm run generate:types

This will generate a file api-types.d.ts which you can now use as TypeScript types in your frontend code:

API TypeScript types generated

Type safe data fetching

Now we can create a type-safe client for data fetching:

import createClient from "openapi-fetch";
const client = createClient<paths>({
baseUrl: "https://demo-type-safe-apis-production.up.railway.app/",
});
export async function getProducts() {
const {
data, // only present if 2XX response
error, // only present if 4XX or 5XX response
} = await client.GET("/products");
return { data, error };
}
type GetProductsResponse = {
data?: {
id: string;
name: string;
price: number;
}[];
};
const productsResponse = (await getProducts()) satisfies GetProductsResponse;

And here is the acutal type generated by OpenAPI TypeScript:

type GetProductsResponse = {
data: {
id: string;
name: string;
price: number;
}[] | undefined;
error: {
message: string;
} | undefined;
};

It works for the entire surface of your API, and is great for validating required body or query parameters:

async function addProduct({ product }: { product: AddProductInput }) {
const {
data, // only present if 2XX response
error, // only present if 4XX or 5XX response
} = await client.POST("/products", {
body: product,
});
return { data, error };
}
const invalidProductResponse = (await addProduct({
// @ts-expect-error - invalid product
product: {
name: "My New Post",
},
}))

Here TypeScript will catch the error at compile time and tell you that you are missing a required property (price in this case).

Show me the code!

For a full example of how this works end-to-end, I created a demo repository which you can clone and run locally.

Type safe data fetching is a great way to improve your developer experience and ship faster and safer. If you are already working with an API that publishes an OpenAPI schema, start generating your TypeScript SDK now and enjoy the benefits!