How do types go from the database to the client?
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.
- Postgres tables define what exists and what constraints apply.
- Application code reads and writes through a typed data layer.
- HTTP or RPC boundaries expose shapes clients can call.
- 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.