HATEOAS & REST

The server owns the state. Hypermedia carries the controls. The client renders what it receives.

SPA

Thin controller squeezes to JSON. Complexity shifts to the client.

server
DTO / Mapping
OpenAPI specs, validation schemas, response models
Controller
JSON over the wire
client (complexity detonates)
API / Fetch Layer
Client Router
Parsing + Validation
State Management
normalized cache, optimistic updates, stale-while-revalidate
Rendering
the user sees a table of users

Where it shines

  • The UI is the application — editors, canvases, design tools
  • Complex local state with rich drag-and-drop interactions
  • Real-time collaboration with shared cursors and CRDTs
  • Offline-first with conflict resolution and background sync

Where it sucks

  • CRUD apps — two state stores that drift apart, connected by load-bearing JSON
  • Every layer generates more layers — DB schema, Go structs, OpenAPI, TS types, Zod schemas
  • Bundle size, hydration cost, time-to-interactive — the user pays for your architecture
  • The DTO/mapping layer exists only to translate between what the DB knows and what JSON needs
  • Two complexity lairs instead of one — and the JSON between them is load-bearing

Hypermedia

Client is thin. Server narrows from fat handler to thin repository. HTML over the wire.

server
Handler
domain logic + templates + hypermedia controls + error affordances
HTML over the wire
client (thin by design)
HTML
HTTP
HTMX
_hyperscript
Alpine.js
inline script
.js files

Where it shines

  • Pages represent resources — the server is the application
  • Content and data-centric workflows with CRUD patterns
  • No serialization layer — no DTOs, no mapping, no OpenAPI
  • Each server layer does less than the one above it
  • Client is intentionally thin — most behavior stays on the server

Where it sucks

  • Rich interactive UIs — spreadsheets, design tools, canvas rendering
  • Offline-first with conflict resolution — the server can't drive what it can't reach
  • Real-time collaborative editing — CRDTs and OT live on the client
  • Every round-trip is latency — some interactions need instant local feedback

Drag the handle or tap either side to compare architectures.

The Reach-Up Model

REST says resources are navigated through hypermedia. HATEOAS says the server tells you what you can do next. Each layer below honors that contract — only reach up when it can't.

Behavior

How resources are discovered and transitioned

reach up
.js files
inline script
Alpine.js
_hyperscript
bridge
HTMX
HTTP
HTML
start here

Presentation

How representations are rendered

reach up
raw CSS
Tailwind
DaisyUI
CSS
start here

Where Your Code Should Live

Most of your application is HTML templates with HTMX attributes. Client-side code is a thin sliver, intentionally. This chart is rendered with Charts.css, a CSS-only charting framework. No JavaScript.

HTML
CSS
HTTP
HTMX
HS
Alpine
JS

The Domain Map

Map two dimensions of the hierarchy — where it runs and what it manages — and six domains emerge. Each tool has a place. Nothing crosses boundaries it shouldn't.

State
Behavior
Presentation
Server

Go + SQL

Source of truth. Resource state. The server owns the data.

HTTP + HTMX

Hypermedia controls. Resource transitions. The server drives the UI.

templ + DaisyUI

Typed components. Semantic markup. Theme-aware rendering.

Client

Alpine.js

View state. Ephemeral. A modal's open flag, not resource data.

_hyperscript

DOM interactions. Transitions. Toggles. No server round-trip.

Tailwind + CSS

Layout. Spacing. Visual adjustments. The cascade at work.

Server row: thick, authoritative. Client row: thin, ephemeral. Nothing in the bottom row pretends to be the top row.

See It In Action

Every pattern described above is implemented in the demo. Browse the pages to see how each layer works.