G

You Don’t Need tRPC for Type-Safe APIs

If you’ve ever built a frontend and backend at the same time, you know the pain.
Did that endpoint change? Is it /v1/customers or /api/customers this time?
And wait,was that field name, full_name, or display_name?

We’ve all been there. Guessing, checking docs, syncing Slack messages, and hoping your TypeScript types match what your API actually returns. It’s the modern version of “it works on my machine.”

When we started building our product, I promised myself: no more guessing games.
We wanted the backend and frontend to speak the same language, literally.

But there was one catch: we weren’t using tRPC. Our backend was written in FastAPI, not TypeScript.

Still, we refused to give up on type safety. Here’s how we built a stack where Python and TypeScript stay perfectly in sync, no manual typings, no runtime surprises.


FastAPI: The Single Source of Truth

FastAPI was our backend of choice because it forces clarity. Every endpoint is defined with Python type hints and Pydantic models. That means your validation, docs, and OpenAPI schema are all generated for free.

Out of the box, FastAPI gives you a live spec at /openapi.json.
That’s not just a nice dev tool, it’s an always-up-to-date contract for your entire system.

During local development, we expose it at http://localhost:8000/openapi.json so the frontend can pull from it directly. The backend defines reality; the frontend just follows.


Auto-Generated Types with openapi-ts

Our philosophy: the backend defines the truth, the frontend imports it.

openapi-ts makes that possible. With one command, it converts your FastAPI OpenAPI schema into TypeScript definitions.

Here’s how easy it is to set up:

1. Install the package

bash
bun add openapi-typescript

2. Add a script in your frontend’s package.json

json
"scripts": {
  "generate-types": "openapi-typescript http://localhost:8000/openapi.json -o src/types/api-schema.ts"
}

Now, running bun generate-types regenerates your API types automatically:

bash
bun run generate-types

You’ll get a file like src/types/api-schema.ts, containing every route, request, and response ready to import anywhere in your app.

Example:

typescript
import type { components } from "./api-schema";

export type User = components["schemas"]["UserSchema"];

No manual maintenance. No mismatched shapes. Just perfectly aligned types.


Type-Safe Fetching with openapi-fetch

Generating types is only half the story, you also need a way to use them.

We didn’t want yet another fetch wrapper. We wanted something that actually knew what endpoints existed, what data they expected, and what they returned.

openapi-fetch does exactly that.
It’s part of the same ecosystem, designed to work seamlessly with openapi-ts.

Here’s the setup:

typescript
import createClient from "openapi-fetch";
import { getConfig } from "@/config";
import type { paths } from "@/types/api-schema"; // generated types

const config = getConfig();

export const apiClient = createClient<paths>({
  baseUrl: config.apiUrl,
  credentials: "include",
});

The result? Instant autocomplete. Compile-time validation.
Change a backend parameter or remove a field, and your frontend won’t even compile.
That’s the kind of failure we like: the safe kind.


The Developer Experience Feels Effortless

Here’s our new workflow in action:

  1. Define a route in FastAPI. Add your path, input, and output models.

  2. Run the backend. FastAPI automatically updates /openapi.json.

  3. Regenerate frontend types.

    bash
    bun run generate-types
    
  4. Use it with openapi-fetch. You get fully typed requests right in your editor.

No manual syncs. No shared constants file. No Slack pings asking,

“Hey, did you rename that field again?”

It’s clean, reliable, and scales beautifully as your API grows.


The Stack That Talks to Itself

Here’s the full picture:

  • FastAPI → defines endpoints and auto-generates OpenAPI
  • openapi-ts → converts schema into TypeScript types
  • openapi-fetch → makes fully typed API calls

Together, they form a self-documenting, type-safe feedback loop between backend and frontend.
It’s not magic, it just feels like it.


Takeaway

You don’t need tRPC to get a type-safe workflow.
If your backend already speaks OpenAPI (and FastAPI does), you’re just a few commands away from full-stack type safety.

Because the best kind of integration?
The one that never drifts out of sync.