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.

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.

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:
-
Eliminate guesswork: you want to know as soon as possible when you frontend and backend code are getting out of sync.
-
Boost your productivity: you do want to write API types by hand and have a manual process to keep them updated.
-
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.

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:
- fastify-swagger for Node.js with Fastify. Example source code
- express-openapi for Node.js with Express
- rswag or rspec-openapi for Ruby
- swag for Go
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:
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:
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:

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!