Client
One typed client, created once, carrying the types your schema produced. Reads and writes, live queries, your own events, and ready-made query options for React.
Your server code calls app.collections.posts. Your browser code calls
client.collections.posts. The options go in the same shape. The rows come back
in the same shape. Both were derived from the same collection file.
The client talks to your app over its own API. So every call it makes runs through your access rules.
Create it once
Put the client in one file and export it. The starters write this file for you.
import { createClient } from "questpie/client";
import type { AppConfig } from "#questpie";
export const client = createClient<AppConfig>({
baseURL: "http://localhost:3000",
basePath: "/api",
});AppConfig is the type codegen writes. #questpie is the alias the starters
point at your generated index. Pass that type and every call below is checked
against your schema.
const { docs } = await client.collections.posts.find({
where: { published: true },
limit: 10,
});docs holds your rows, typed. Rename a field, run questpie generate, and that
where stops compiling.
`basePath` defaults to `/`
The starters mount the API handler at /api and pass basePath: "/api" to
match. Leave it out and requests go to /posts instead of /api/posts.
What the client holds
| Member | What it is |
|---|---|
client.collections.<name> | Find, create, update, delete, versions, uploads. |
client.globals.<name> | get, update, versions and live reads on a one-row global. |
client.routes.<path>.<method> | Your own routes. One leaf per HTTP method. |
client.search | search() across indexed collections, and reindex(). |
client.channels.<name> | Publish and subscribe to your own typed events. |
client.realtime | The one multiplexed connection behind every live() call. |
client.setLocale(locale) | Sets the accept-language header on later requests. |
Collaborative documents are missing from that table on purpose. Build them with
createCrdtClient(client) from questpie/crdt. It reuses this client's
realtime connection. Reading client.crdt throws and points you at it.
Two names that mean two things
updateById, deleteById and restoreById mean the same thing on the server
and on the client. Prefer them. update and delete do not. On the client they
take one id. On the server they take a where and hit every matching row.
The pages
| Page | What it covers |
|---|---|
| Client SDK | Every method on the client, and the route each one calls. |
| TanStack Query | Collections, globals, routes and channels as ready query options. |
| Realtime | A read that keeps arriving. live() in place of find(). |
| Channels | Your own events. Chat, progress, typing, presence. |
| Collaborative documents | Two people editing one field at the same time. |
| Reactive Apps | Which live primitive to pick, and what each one costs. |
| Client internationalization | Locale strings, dates and numbers in your own UI. |
If you use React
You do not have to call the client directly. @questpie/tanstack-query takes
the client you already made. It returns builders for your collections, globals,
routes and channels.
import { createQuestpieQueryOptions } from "@questpie/tanstack-query";
import { client } from "@/lib/client";
export const q = createQuestpieQueryOptions(client);Each builder hands back a finished options object, so a component is one line.
const { data } = useQuery(q.collections.posts.find({ limit: 10 }));Query keys, fetchers and error mapping are already in there. Pass
{ realtime: true } as a second argument and the same query goes live. It uses
the connection the client already holds.
client.search is the one member with no builder. Wrap it yourself with
q.custom.query({ key, queryFn }).
Three ways to stay current
A live query is a find() that keeps arriving. Use it for rows that must still
be right after a reconnect.
A channel is your own event, validated on the way through. Use it for things you do not need to store. A progress bar, a typing indicator.
A collaborative document is one field two people type into at once. It pulls in a client-side engine of about 164 KB, so use it only where you need it.
Reactive Apps works through that choice in full.
Where the line falls
A call from a script or a job runs in system mode by default. Every access rule returns true.
A call from the client runs as user. It carries the session cookie. Your
rules are checked on every request. Add getAuthHeaders if you hold a bearer
token rather than a cookie. It runs before each request, so a rotated token
needs no new client.
The client is not a way around access control. It is the thing access control was written for. Access control covers the rules themselves.
Next
Client SDK is the full method list, with the arguments each one takes and the route it calls.