IONICWEBCREATOR
Engineering Journal
Engineering

Type-safe from the database to the button

A walk through how we thread a single source of truth from a Postgres schema all the way to a React component — and what that end-to-end guarantee actually buys a team.

Jakir Hussian

Jakir Hussian · Founder & Engineering Lead

May 12, 20269 min read

Share

Updated Jun 2, 2026

Most bugs we ship are not logic errors. They are shape errors — a field that was optional here and required there, a number that arrived as a string, a rename that updated four call sites and missed the fifth. The fix is not more tests. It is making the wrong shape impossible to write down.

One source of truth

The schema is the root. Everything downstream is a projection of it, and every projection is derived — never hand-maintained in parallel. When the column changes, the type changes, and every consumer that no longer type-checks lights up in the editor.

schema.prismaprisma
model Article {
  id          String   @id @default(cuid())
  slug        String   @unique
  title       String
  publishedAt DateTime?
  author      Author   @relation(fields: [authorId], references: [id])
  authorId    String
}

The generated client gives us a row type for free. We never redeclare it — we import type { Article } and let inference carry it the rest of the way.

The three seams

There are exactly three places a shape can drift. Name them, and you know where to put the guarantee:

  1. Database → server: the query result. Owned by the generated client.
  2. Server → client: the serialised payload. Owned by a shared types package.
  3. Client → screen: the component props. Owned by the same shared type.

Deriving, not duplicating

A card rarely needs the whole record. That is fine — derive a narrower view with a utility type, so it stays tied to the source:

article-card.tsxtypescript
type CardView = Pick<Article, "slug" | "title" | "excerpt">;

export function ArticleCard({ article }: { article: CardView }) {
  return <a href={`/engineering-journal/${article.slug}`}>{article.title}</a>;
}
The best type is the one you never wrote. Derive it, and a rename in the schema walks all the way to the button on its own.
Internal engineering handbook

What it buys you

Without end-to-end typesWith them
Rename ripples through PRs and QARename fails the build at every stale site
API drift found in productionDrift found in the editor
Docs describe the shapeThe type is the docs
One definition, three projections — each derived, none hand-maintained.

For the full pattern applied to a real build, see our case studies — the same thread runs through every one.


  • #TypeScript
  • #Prisma
  • #Server Components
  • #Developer Experience
Newsletter

New essays, in your inbox.

Occasional, considered writing on engineering products that scale. No cadence promises, no noise — only when we have something worth your time.

One or two emails a month. Unsubscribe anytime.

Building something that has to scale?

If the way we think about engineering matches the standard you're after, tell us what you're building. We'll be honest about fit and outline a concrete first step.