Skip to content

Reading and writing data from code

The entity calls an app makes: list, filter, get, create, update, delete, bulk operations and live-updating lists, with what every row carries.

Last updated September 17, 2026

On this page

Pages talk to the database through the SDK's entity helper. This is what the builder writes for you; it is here so you can read the code it produces, or write your own.

JavaScript
import { entity } from "@praxivara/sdk";

const rows = await entity("Booking").list();                       // newest first
const mine = await entity("Order").list({ mine: true });           // only the signed-in user's rows
const page = await entity("Order").list({ limit: 20, offset: 40, sort: "-total" });
const open = await entity("Task").filter({ status: "open" });      // equality
const big  = await entity("Deal").filter({ amount: { op: "gte", value: 10000 } });
const one  = await entity("Order").get(id);

await entity("Booking").create({ client_name, date });
await entity("Booking").bulkCreate([{ ... }, { ... }]);
await entity("Task").update(id, { done: true });
await entity("Task").delete(id);                                   // soft delete
await entity("Task").bulkUpdate([{ id, done: true }, { id: id2, done: false }]);
await entity("Task").updateMany([id1, id2], { archived: true });
await entity("Task").deleteMany([id1, id2]);

const stop = entity("Task").subscribe(rows => setTasks(rows), { mine: true });
// later: stop();

Reading

  • list(options) returns rows newest first. Options: limit and offset for paging, sort as a field name with a leading - for descending, and mine: true to keep only the signed-in user's rows.
  • filter(conditions, options) takes a field-to-value map. A plain value means equals; an object with op and value compares with eq, ne, lt, lte, gt, gte or contains.
  • get(id) returns one row.
  • subscribe(callback, options) keeps a list up to date as rows change and returns a function that stops it. Use it for dashboards and inboxes.

What every row carries

Besides its own fields, each row has id, created_date, and mine (true when the signed-in caller created it). A signed-in reader also sees created_by. Use mine to decide whether to show an Edit or Delete control.

Writing

  • create(data) adds a row. Required fields and enum values are validated on the server; an invalid row throws with the field named.
  • update(id, data) merges: fields you leave out keep their values.
  • delete(id), remove(id) and archive(id) are one and the same call, a soft delete.
  • The bulk forms (bulkCreate, bulkUpdate, updateMany, deleteMany) make one request for many rows and are the right choice for anything more than a handful.

Errors

Every call throws on failure. Wrap it, show a real error state, and treat err.status === 401 as "sign in first" (send the visitor to the login page). A permission refusal comes back as a 403 with admin_required or forbidden.

JSX
const [tasks, setTasks] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
  entity("Task").list({ mine: true })
    .then(setTasks)
    .catch(err => {
      if (err.status === 401) navigate("/login");
      else setError(err.message);
    });
}, []);

Backend functions use the same tables

Inside a backend function the same operations are available as ctx.entity("Name"), with slightly different return shapes. See the backend function reference.

Was this page helpful?