Reference

Choosing a data source

Actions, API routes, HTTP or realtime: which one to use, what each kit chose and how they mix.

kitr has four ways to get data: http, api, actions and realtime. Components read all four the same way, through Query. This page says which one to pick.

The matrix

Where the data comes from Use Why
Your app calling its own server actions Call a server function like a local one. No URL to design.
A server that other programs also call, such as a mobile app api Real URLs with a typed client and an OpenAPI document.
An API someone else owns http A REST client with login tokens and errors in one shape.
Updates the server pushes while the screen is open realtime Live channels that update the screen without a refetch.

Two questions settle most cases. Does anything besides your own app call this code? Then use an API route, otherwise an action. Is the server in another repository? Then use http.

What each kit chose

Kit Packages Why
app http It talks to a backend that already exists.
fullstack http, actions, api, realtime One of each, to show the choice. Delete the ones you don't need.
saas http, api A product often grows a mobile app later, which can call the same API.
server api, realtime A backend has no UI of its own, so everything is an API route.
realtime (preset) api, realtime Messages go through a controller, and the channel pushes new ones.
site (preset) api A marketing site posts one form, to a controller.

Can I mix them?

Yes. One screen can read from an action, http and a channel at once. All four share:

  • One error shape. Every failure is a KitrError with code, status, message and fieldErrors, so forms show server errors the same way.
  • One query key shape. ['action', 'projects.list'], ['api', 'projects.show', { id }]. A mutation can refresh a list loaded from another source.
  • The same login headers. http and api send the token and CSRF header the same way.
  • Cancellation. Every source stops when its query is cancelled or times out.

Decide per feature, not per project. If the UI and a mobile app need the same operation, write one API route and call it from both.

Using the shared module directly

Your own package can reuse these pieces from @kitr/core/request:

import { applyRequestHeaders, fromEnvelope, keyFor, linkSignals, toEnvelope } from '@kitr/core/request'

const key = keyFor('billing', 'invoices.list', { status: 'open' })

const response = await fetch('/billing/invoices', {
  headers: applyRequestHeaders(new Headers(), { method: 'GET', token }),
  signal: linkSignals(callerSignal, AbortSignal.timeout(10_000)),
})
if (!response.ok) throw fromEnvelope(await response.json().catch(() => undefined), response.status)
Export What it does
toEnvelope(error, { mask }) Turn an error into the JSON sent over the wire. mask hides server details.
fromEnvelope(json, status) Turn an error response into a KitrError, including common shapes from other backends.
okEnvelope, failEnvelope, unwrapEnvelope The { ok, data } | { ok: false, error } result that actions return from a server function.
keyFor(source, name, ...parts), serializeKey(key) The query key, and a stable string for it.
linkSignals(...signals), timeoutSignal(ms), withTimeout(signal, ms) One signal from several, and a timeout as a signal.
applyRequestHeaders(headers, options) Add the login token and CSRF header without replacing your own headers.