How do types go from the database to the client?

typescript drizzle postgresql product-engineering
Types moving from database schema to client code

The bug arrives as a polite 500. Support pastes the ticket. Engineering opens the dashboard component. Everything looks fine locally. Production data has a nullable field the UI never handled because the API type was hand written six months ago and nobody updated it when the migration landed. The postmortem calls it a communication gap. The fix is structural: one typed path from the database to the client so drift becomes a build failure instead of a customer surprise.

Founders do not need to implement every layer themselves. They do need to recognize when their stack treats the schema as folklore versus when it treats the schema as the spine types ride on.

The vertical slice in plain language

Picture four stations on one assembly line.

  1. Postgres tables define what exists and what constraints apply.
  2. Application code reads and writes through a typed data layer.
  3. HTTP or RPC boundaries expose shapes clients can call.
  4. UI code renders fields it expects and handles errors it understands.

Types are the luggage tags on objects moving between stations. When tags match at every hop, refactors stay boring. When someone rewrites tags by hand at station three, the line lies.

PostgreSQL remains the durable memory. Drizzle is a common TypeScript first way to declare tables, generate migrations, and infer query result types. The client might be HTMX swapping HTML, a React app, or a mobile shell. The typed path principle does not care about the renderer. It cares that nobody invents a parallel schema in the front end.

Station one: schema as source of truth

Start where the data lives. Tables, columns, nullability, enums, and foreign keys are product decisions encoded in SQL. When those decisions live only in a diagram or a senior engineer's head, every downstream type is guesswork.

Schema first workflows check definitions into Git. Migrations apply in order. Preview databases on hosts like Neon exercise migrations before merge. That loop is documented in What is Drizzle and in What is Neon. The type story begins here because Drizzle infers TypeScript types from the same definitions that emit SQL.

Example mental model:

Database fact TypeScript effect
email is text not null unique Inserts require a string; duplicates fail at the database
status is an enum Application code gets a union of allowed strings
Relation to orders Queries can join with typed nested objects

When the schema changes, the migration file and the TypeScript definitions change together in one pull request. Reviewers see product impact and type impact at once.

Station two: application layer honesty

Server code should not cast query results to any at the door. Drizzle queries return types tied to table definitions. Hand written SQL can still be typed with explicit result interfaces, but those interfaces must be updated when columns change.

This layer is also where business rules live that the database cannot express cleanly. A column might be nullable in Postgres because legacy rows exist, but new API responses might guarantee a default. Document that choice in the function that shapes the response, not in a comment buried in the UI.

Patterns that work for small teams:

One module owns data access. Routes call service functions. Service functions call Drizzle. UI routes do not embed raw SQL strings.

Map database rows to API DTOs on purpose. Sometimes the client should not see internal columns. Explicit mapping functions make omissions visible in review instead of accidental leaks through spread operators.

Test migrations against typed queries. CI applies migrations to a throwaway database, then runs tests that exercise the typed accessors. Types catch renames. Tests catch logic.

Station three: the boundary clients call

Clients never talk to Postgres directly in a well behaved product. They talk to HTTP, WebSockets, or RPC. That boundary is where untyped stacks usually break down. Someone generates OpenAPI from nowhere, or worse, front end developers read network tabs and invent interfaces.

Better options:

Shared package. Monorepos export types from the server module clients import. Change propagates instantly. Build fails if versions mismatch.

Schema generated OpenAPI. Tools derive HTTP types from route definitions or from database exposed layers like PostgREST. The generation step must run in CI so drift fails builds.

Server driven UI with typed fragments. HTMX and similar approaches still benefit from typed partials on the server even when the browser receives HTML instead of JSON. The contract is what fields the template assumes.

How do you connect a simple front end to a typed API? covers the front edge of this station. The theme is the same: pick the contract before debating widget libraries.

Station four: client consumption without fantasy types

The UI layer should import or generate types from the boundary, not recreate them. When designers add a field to a settings screen, the workflow is: migration, server type update, client type update, component change. Four steps linked, not three steps plus hope.

For React teams, props mirror API DTOs. For HTMX teams, server templates receive typed view models. For mobile, codegen from OpenAPI produces Swift or Kotlin structs. The mechanism varies. The rule does not.

Client code still validates at runtime for untrusted input. Types do not replace sanitization on user generated content. They do replace guessing about your own API responses.

Where the flow breaks in real companies

Knowing the ideal path helps you spot shortcuts that will tax you later.

Parallel type files maintained by hand. User.ts in the front end and User.ts in the back end diverge quietly until a launch week.

Optional everything. TypeScript allows ? on every field when requirements are fuzzy. The compiler stays green while the product becomes impossible to reason about.

Skipping CI on type checks for speed. Preview deploys that ignore types teach the team that green builds lie.

Leaking raw database rows to clients. Even perfect types cannot fix exposing internal identifiers or soft deleted records because someone returned ORM entities directly.

Framework churn without boundary stability. Rewriting React to something else hurts less when API types stayed stable. Rewriting everything at once is a company event, not a refactor.

A minimal stack that respects the flow

You do not need seventeen services. A sensible default for a product team on Postgres:

Piece Role
Postgres Durable records and constraints
Drizzle Schema, migrations, typed queries
Node or Bun Application server
Typed routes HTTP handlers with shared input/output types
Thin client HTMX or React consuming the same shapes

Preview branches apply migrations before merge. TypeScript runs in CI. Client bundles import types or generated clients from the same commit hash as the server they call.

That stack is not the only valid stack. It is an example where types have a clear upstream owner: the schema.

Governance without a platform team

Small teams skip governance when it sounds enterprise. Typed vertical slices are governance you can afford.

Pull requests include schema, server, and client touch points together. Feature work that changes data shape should not merge as application only diffs.

Renames are product events. BI tools, exports, and integrations read column names. Types surface code impact. Humans still own customer comms.

Document breaking API changes in release notes. Types catch internal drift. Partners and mobile apps lagging store approval need words.

Prefer one obvious path for new engineers. "Where do types come from?" should have one answer: "Follow the schema forward."

Why founders should care

Users experience type drift as broken screens, wrong totals, and forms that submit but do not save. Finance experiences it as numbers that do not reconcile. Support experiences it as "works on my machine" from engineering.

A typed path from database to client is how product teams align what the vault stores with what the screen promises. It is not bureaucracy. It is how five people simulate the discipline larger companies buy headcount for.

TypeScript is the usual language for this story because the checker exists and the tooling matured. The story itself is older: single source of truth, propagated with mechanical help, verified before deploy.

When your stack does that, migrations stop feeling like roulette. UI work stops guessing field names. Founders stop hearing "we just missed that edge case" for problems the compiler could have flagged Tuesday.

Work with Kleto

I am James Cowan, a product engineer and the founder of Kleto. Kleto is a product engineering agency that ships production software from strategy through handoff. We wire Postgres, Drizzle, and typed API boundaries so your schema, server, and client stay one story as the product grows. If that matches your stack, contact Kleto and we will scope a sensible first step.

Recommended

How do you connect a simple front end to a typed API? @jameslcowanjul 16 '26 typescript, javascript
What is TypeScript, and why do product teams still adopt it? @jameslcowanmar 31 '26 typescript, javascript
What is Drizzle, and why do teams treat the schema as a contract? @jameslcowanmay 12 '26 drizzle, typescript
What is SQLite good for at the edge of a system? @jameslcowanjul 13 '26 sqlite, postgresql
What is PostgreSQL, and why do products still bet on it? @jameslcowanapr 29 '26 postgresql, product-engineering
What is the difference between product engineering and \"just development\"? @jameslcowanjul 9 '26 product-engineering, startups
What is observability when you do not have a platform team? @jameslcowanjul 1 '26 observability, product-engineering
What is the total cost of \"we will just use X\"? @jameslcowanjun 24 '26 smb, product-engineering
What makes an internal tool survive the first hire? @jameslcowanjun 18 '26 smb, documentation
What does \"full stack\" mean on a vendor quote? @jameslcowanjun 10 '26 smb, product-engineering
When is REST from the database enough? @jameslcowanjun 3 '26 postgrest, postgresql
What is Supabase, and what are you actually buying? @jameslcowanmay 28 '26 supabase, postgresql
What is the difference between Postgres and PostgREST? @jameslcowanmay 19 '26 postgresql, postgrest
What is Neon, and how do preview databases change shipping? @jameslcowanmay 7 '26 neon, postgresql
What is React, and when is the weight worth it? @jameslcowanapr 22 '26 react, javascript
What is the difference between Bun and Node? @jameslcowanapr 14 '26 bun, javascript
Anthropic Trained Its Replacement @jameslcowanjun 9 '26 anthropic, ai
Karpathy Was Wrong: OpenClaw Still Outruns Its 5 Real Alternatives @jameslcowanjun 11 '26 openclaw, ai
OpenClaw: What It Is and How to Get the Most Out of It @jameslcowanjun 5 '26 openclaw, javascript
Pydantic: The Open Source Layer Quietly Running the AI Economy @jameslcowanjun 22 '26 pydantic, python
The YC S26 Deadline Just Closed. Here's What Separates the 1.5% From Everyone Else. @jameslcowanjun 17 '26 startups, yc
OpenClaw 2026.2.23: The Agent Browser Upgrade @jameslcowanjun 19 '26 openclaw, ai
Why I Skipped the CMS @jameslcowanjun 10 '26 markdown, static-site-generation
Fixing Navigation and Analytics: When Your Data Lies About User Behavior @jameslcowanjun 5 '26 htmx, javascript
HAL: Cutting 100-300KB of JavaScript by Moving Routing to Build Time @jameslcowanjun 10 '26 htmx, javascript
Mobile-First Layout That Ships: How PrimaryLayout Solves Real UX Problems @jameslcowanjun 6 '26 htmx, css
Documentation That Scales: Constitution, Contracts, and Runbooks @jameslcowanjun 8 '26 documentation, markdown
Zero-Server Analytics: How I Replaced a SaaS Bill with Netlify Functions and GitHub @jameslcowanjun 8 '26 netlify, analytics
Web Components + HTMX: A Lean Architecture for Content Sites That Ship @jameslcowanjun 10 '26 web-components, javascript
HAL: Build-Time Link Rewriting That Makes Navigation Feel Instant @jameslcowanjun 18 '26 htmx, javascript
Why I Built My Own Analytics Pipeline (And What It Actually Costs) @jameslcowanjun 18 '26 analytics, javascript
The Static Site Playbook: Shipping a Content Product on a Near-Zero Budget @jameslcowanjun 11 '26 static-site-generation, javascript
How This Site Works: Architecture for a One-Person Team @jameslcowanjun 8 '26 htmx, javascript
Web Components as a Business Decision @jameslcowanjun 11 '26 web-components, javascript
Progressive Enhancement with HTMX: Ship Fast, Degrade Gracefully @jameslcowanjun 8 '26 htmx, javascript
Less JavaScript, More Leverage: Why I Ship With a 35KB Budget @jameslcowanjun 22 '26 javascript, performance
Static Site Generation: The Business Case for Pre-Rendered HTML @jameslcowanjun 14 '26 static-site-generation, performance

Recommended

How do you connect a simple front end to a typed API? @jameslcowanjul 16 '26 typescript, javascript
What is TypeScript, and why do product teams still adopt it? @jameslcowanmar 31 '26 typescript, javascript
What is Drizzle, and why do teams treat the schema as a contract? @jameslcowanmay 12 '26 drizzle, typescript
What is SQLite good for at the edge of a system? @jameslcowanjul 13 '26 sqlite, postgresql
What is PostgreSQL, and why do products still bet on it? @jameslcowanapr 29 '26 postgresql, product-engineering
What is the difference between product engineering and \"just development\"? @jameslcowanjul 9 '26 product-engineering, startups
What is observability when you do not have a platform team? @jameslcowanjul 1 '26 observability, product-engineering
What is the total cost of \"we will just use X\"? @jameslcowanjun 24 '26 smb, product-engineering
What makes an internal tool survive the first hire? @jameslcowanjun 18 '26 smb, documentation
What does \"full stack\" mean on a vendor quote? @jameslcowanjun 10 '26 smb, product-engineering
When is REST from the database enough? @jameslcowanjun 3 '26 postgrest, postgresql
What is Supabase, and what are you actually buying? @jameslcowanmay 28 '26 supabase, postgresql
What is the difference between Postgres and PostgREST? @jameslcowanmay 19 '26 postgresql, postgrest
What is Neon, and how do preview databases change shipping? @jameslcowanmay 7 '26 neon, postgresql
What is React, and when is the weight worth it? @jameslcowanapr 22 '26 react, javascript
What is the difference between Bun and Node? @jameslcowanapr 14 '26 bun, javascript
Anthropic Trained Its Replacement @jameslcowanjun 9 '26 anthropic, ai
Karpathy Was Wrong: OpenClaw Still Outruns Its 5 Real Alternatives @jameslcowanjun 11 '26 openclaw, ai
OpenClaw: What It Is and How to Get the Most Out of It @jameslcowanjun 5 '26 openclaw, javascript
Pydantic: The Open Source Layer Quietly Running the AI Economy @jameslcowanjun 22 '26 pydantic, python
The YC S26 Deadline Just Closed. Here's What Separates the 1.5% From Everyone Else. @jameslcowanjun 17 '26 startups, yc
OpenClaw 2026.2.23: The Agent Browser Upgrade @jameslcowanjun 19 '26 openclaw, ai
Why I Skipped the CMS @jameslcowanjun 10 '26 markdown, static-site-generation
Fixing Navigation and Analytics: When Your Data Lies About User Behavior @jameslcowanjun 5 '26 htmx, javascript
HAL: Cutting 100-300KB of JavaScript by Moving Routing to Build Time @jameslcowanjun 10 '26 htmx, javascript
Mobile-First Layout That Ships: How PrimaryLayout Solves Real UX Problems @jameslcowanjun 6 '26 htmx, css
Documentation That Scales: Constitution, Contracts, and Runbooks @jameslcowanjun 8 '26 documentation, markdown
Zero-Server Analytics: How I Replaced a SaaS Bill with Netlify Functions and GitHub @jameslcowanjun 8 '26 netlify, analytics
Web Components + HTMX: A Lean Architecture for Content Sites That Ship @jameslcowanjun 10 '26 web-components, javascript
HAL: Build-Time Link Rewriting That Makes Navigation Feel Instant @jameslcowanjun 18 '26 htmx, javascript
Why I Built My Own Analytics Pipeline (And What It Actually Costs) @jameslcowanjun 18 '26 analytics, javascript
The Static Site Playbook: Shipping a Content Product on a Near-Zero Budget @jameslcowanjun 11 '26 static-site-generation, javascript
How This Site Works: Architecture for a One-Person Team @jameslcowanjun 8 '26 htmx, javascript
Web Components as a Business Decision @jameslcowanjun 11 '26 web-components, javascript
Progressive Enhancement with HTMX: Ship Fast, Degrade Gracefully @jameslcowanjun 8 '26 htmx, javascript
Less JavaScript, More Leverage: Why I Ship With a 35KB Budget @jameslcowanjun 22 '26 javascript, performance
Static Site Generation: The Business Case for Pre-Rendered HTML @jameslcowanjun 14 '26 static-site-generation, performance