How do you connect a simple front end to a typed API?

typescript htmx postgrest product-engineering javascript
Simple front end talking to a typed API

Teams waste months arguing frameworks when the real question is simpler: how does the browser talk to the source of truth without lying about shapes and permissions?

You can answer that with a heavy SPA and a custom backend. You can also answer it with a thin UI and a typed API that stays honest as the product grows. Both paths work. The failure path is also common: a fashionable client that reimplements business rules in three stores while the server quietly drifts.

This is the seam between "ship a screen" and "own a system." Get it wrong and every feature ships twice, once in the UI and once in the API, with nobody sure which one is correct.

My default for small teams: pick the contract first, keep the client humble, and make type drift fail in CI before customers feel it.

Start from the contract, not the widgets

Pick the contract before you pick a component library. UI libraries come and go. A muddy contract stays muddy in every library.

Answer these first:

If you cannot answer those on one page, you are not ready for a framework debate. You are ready for a whiteboard and a uncomfortable hour about permissions.

How types flow from the database to the client covers the vertical slice. Here the focus is the front edge of that slice: the boundary where user intent becomes HTTP and HTTP becomes something your database can trust.

Three patterns that actually show up in production

Pattern A: schema driven HTTP. If your API is mostly table shaped, PostgREST or a Supabase style layer can expose Postgres directly. The front end calls HTTP. Types can be generated from OpenAPI or from a shared schema package.

This shines for admin tools and internal products where the screen maps cleanly to rows. It struggles when the UI needs a choreography the database should not own. Multi step approvals, external webhooks, and "do five things atomically" belong in a service, not in clever SQL the next hire cannot read.

Pattern B: small API, shared types. A modest Node or Bun service owns routes. Types are shared from a package or generated from Zod, TypeBox, or similar. The front end imports those types or OpenAPI clients.

You pay for a thin backend. You get a place to put workflows, webhooks, and rules that are not row shaped. For most customer facing products with real business logic, this is my default.

Pattern C: HTML first with progressive enhancement. A simple front end does not have to mean a giant client runtime. Server rendered HTML plus small bursts of JavaScript (or HTMX style swaps) can talk to the same typed API. The browser gets documents and fragments. The API still enforces auth and shapes.

This site uses that philosophy for content. Product apps can use the same instinct: keep the client humble when the server already knows the truth. Less JavaScript in the browser often means fewer places for business rules to hide.

What "typed" should mean to a founder

Typed does not mean "we use TypeScript somewhere." Plenty of codebases have types on the client and chaos on the server.

Typed means a change in the data contract fails loudly in CI before customers feel it. If your UI can send fields the server no longer accepts and nobody notices until production, you do not have a typed boundary. You have vibes with syntax highlighting.

That distinction matters when you hire. A team that treats types as a shared contract ships faster long term. A team that treats types as a front end fashion choice rebuilds the same screen every year.

A wiring checklist I use on new projects

  1. Auth before cosmetics. Tokens, cookies, or session headers with clear rules. Know what unauthenticated means on every route.
  2. One base URL per environment. Staging and production are not optional. Hardcoding production URLs in a demo is how incidents start.
  3. Generated or shared types in CI. Break the build when the contract drifts. This is non negotiable for me.
  4. Error shapes the UI can show. Silent JSON failures train users to distrust the app. { "error": "..." } beats a blank screen.
  5. Idempotency on money and invites. Retries happen. Networks lie. Plan for them on the paths that hurt.

Choosing simplicity on purpose

If your team is small, prefer the least moving parts that still protect the contract. A simple front end plus a clear API beats a fashionable client that reimplements business rules because the framework made it easy to add state in the wrong layer.

When REST from the database is enough, use it. When it is not, read When is REST from the database enough? and put a real service on the awkward paths only. Do not put a service everywhere because enterprise blogs said you should.

The incentive in most stack debates is career safety, not product fit. Teams default to heavy clients because that is what hiring managers recognize. Founders pay for that recognition in complexity they do not need yet.

What I would tell a builder in the same spot

Here is what broke for me: treating the front end as the place where business rules lived because React made it fast to prototype. Here is what we did instead: server owns rules, client owns presentation, shared types enforce the seam. Here is what I would not do again: skip the contract conversation because "we will figure out the API later."

Later is where permissions leak, duplicate validation lives in two languages, and every new screen becomes a archaeology project.

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 help teams wire thin clients to honest APIs without accidental architecture. If that matches your stack, contact Kleto and we will scope a sensible first step.

Recommended

What is TypeScript, and why do product teams still adopt it? @jameslcowan typescript, javascript
Types without the build step: why I chose JSDoc + tsc over TypeScript files @jameslcowan typescript, javascript
How do types go from the database to the client? @jameslcowan typescript, drizzle

Recommended

What is TypeScript, and why do product teams still adopt it? @jameslcowan typescript, javascript
Types without the build step: why I chose JSDoc + tsc over TypeScript files @jameslcowan typescript, javascript
How do types go from the database to the client? @jameslcowan typescript, drizzle

Search by title, tag, description, or the prose itself. Results appear as you type.