Skip to content
Part 2: The Foundation 10 min read

Lesson 04

Type Safety Pipeline

One Schema, Zero Drift, Zero Runtime Errors

Pop quiz: how do you know your frontend and backend agree on what a "user" looks like?

If the answer involves "we keep them in sync manually" — you already know the pain. Someone renames a field on the backend. The frontend doesn't know. Nothing breaks at build time. Everything breaks at 2am when real users see blank screens.

Here's a different answer: you define the shape once, and everything else is generated.

The pipeline

Pydantic Schema (Backend)
        ↓  FastAPI auto-generates
OpenAPI Specification (JSON)
        ↓  openapi-typescript
TypeScript Types (Frontend)

You write the Pydantic schema. FastAPI turns it into an OpenAPI spec automatically. One npm script turns that spec into TypeScript types. Change a field on the backend? The TypeScript compiler tells you every component that needs updating — before you push, before it reaches production.

Step 1: Define your schema with constraints

class TaskCreate(BaseModel):
    project_id: UUID
    assignee_id: UUID
    due_date: datetime
    story_points: int = Field(default=3, ge=1, le=21)
    priority: TaskPriority  # Enum
    description: str = Field(..., min_length=3, max_length=500)

Notice the constraints — ge=1, le=21, min_length=3. These become part of your API spec. Frontend validation knows the rules without you writing them twice.

Step 2: Wire it into FastAPI

@router.post("/", response_model=TaskResponse, status_code=201)
async def create_task(data: TaskCreate):
    ...

Step 3: Generate TypeScript types (one command)

npx openapi-typescript http://localhost:8000/openapi.json -o src/types/api.ts

Step 4: Use them everywhere

// TypeScript now knows every valid task status
const statusColors: Record<TaskStatus, string> = {
  open: "blue",
  in_progress: "green",
  // Add a new status? Compiler demands you handle it here.
};

Six months of results

MetricResult
Compile-time errors caught847
Runtime type errors in production0
Migration speed improvement57% faster
AI generation accuracy improvement+30%

Pitfalls to avoid

  • The any escape hatch: Ban it with ESLint's no-explicit-any: "error". Every any is a hole in your safety net.
  • Manual type duplication: The moment you hand-write a TypeScript interface that mirrors a backend model, you've created a drift opportunity. All API types must come from generated types.
  • Skipping strict mode: Enable strict: true in tsconfig.json. Strict mode catches the bugs that permissive mode lets through.

Add it to your CI

# In your CI pipeline:
- Start backend (for OpenAPI spec)
- Run: openapi-typescript → generates types
- Check: git diff --exit-code src/types/  # Fail if types changed but weren't committed
- Run: tsc --noEmit                       # Full type check

No type drift gets past this gate. If the backend changes a schema and the frontend types aren't regenerated, CI catches it before merge.