Skip to content

AffordantReach REST level 3, both sides of the wire

Hypermedia controls (HATEOAS) are the REST maturity level most teams never reach. Affordant gets you there — the server declares the actions it offers; your UI renders off them, with no authorization re-implemented in the frontend.

The idea in thirty seconds

Your frontend never builds a URL, never picks an HTTP verb, never duplicates an authorization rule. It asks three questions:

ts
import { can, actionFor, follow } from 'affordant'

if (can(order, 'cancel')) {                        // 1. What is the server offering me?
  await follow(actionFor(order, 'cancel')!, {      // 2. Where / how?  3. Do it.
    token: () => localStorage.getItem('token'),
    body: { reason: 'changed my mind' },
  })
}

If the backend stops offering an action — not authorized, wrong state, feature off — the button disappears. No frontend deploy.

The other side of the wire

The server declares those same affordances once, gating each on authoritative state. When when is false, the rel is never emitted — so can() returns false on the client.

ts
import { resource } from '@affordant/server'

resource(order)
  .self(route('orders.show', order.id))
  .action('cancel', route('orders.cancel', order.id), {
    method: 'POST',
    when: caller.id === order.ownerId && order.status !== 'shipped',
  })
  .build()

One contract, never two implementations to keep in sync. See the packages for the whole family.

Released under the MIT License.