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
KitrErrorwithcode,status,messageandfieldErrors, 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.
httpandapisend 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. |