QUESTPIE

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.

View markdown

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.

src/lib/client.ts
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

MemberWhat 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.searchsearch() across indexed collections, and reindex().
client.channels.<name>Publish and subscribe to your own typed events.
client.realtimeThe 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

PageWhat it covers
Client SDKEvery method on the client, and the route each one calls.
TanStack QueryCollections, globals, routes and channels as ready query options.
RealtimeA read that keeps arriving. live() in place of find().
ChannelsYour own events. Chat, progress, typing, presence.
Collaborative documentsTwo people editing one field at the same time.
Reactive AppsWhich live primitive to pick, and what each one costs.
Client internationalizationLocale 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.

On this page