Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/michael-tiger-2010/dragonjson/llms.txt

Use this file to discover all available pages before exploring further.

The server/nodejs/server.js file is a complete, zero-dependency reference implementation of the dragonJSON server protocol. It uses only Node.js built-ins (http and url) — no npm install required. The in-memory store can be swapped for any persistence backend by replacing three functions: getByPath, setByPath, and deleteByPath.

Starting the Server

node server/nodejs/server.js
# or with a custom port:
PORT=8080 node server/nodejs/server.js
The server starts on port 3000 by default, logs a confirmation message, and enables CORS for all origins so browser clients can connect without a proxy.
dragonJSON server listening on http://localhost:3000

Data Store

The in-memory store object at the top of server.js is the entire database. It is a plain JavaScript object that mirrors the shape of your API:
let store = {
  meta: { title: "dragonJSON Demo", version: "1.0.0" },
  users: {
    user1: { name: "Alice", role: "admin" },
    user2: { name: "Bob", role: "member" },
  },
  posts: {
    page1: { title: "Hello World", body: "First post body." },
    page2: { title: "Second Post", body: "Second post body." },
  },
};
To connect a persistent store — file I/O, SQLite, Redis, a database ORM — replace the three path-manipulation functions below. Everything else in the server stays the same.
getByPath(obj, segments)
function
Reads a value by following the given path segments through obj. Returns undefined if any segment along the way is missing or not an object.
function getByPath(obj, segments) {
  let cur = obj;
  for (const seg of segments) {
    if (cur === null || typeof cur !== "object") return undefined;
    cur = cur[seg];
  }
  return cur;
}
setByPath(obj, segments, value)
function
Writes value at the location described by segments. Creates intermediate objects along the path if they do not exist.
function setByPath(obj, segments, value) {
  let cur = obj;
  for (let i = 0; i < segments.length - 1; i++) {
    const seg = segments[i];
    if (cur[seg] === undefined || typeof cur[seg] !== "object") {
      cur[seg] = {};
    }
    cur = cur[seg];
  }
  cur[segments[segments.length - 1]] = value;
}
deleteByPath(obj, segments)
function
Deletes the key at the location described by segments. Returns true if the key existed, false otherwise.
function deleteByPath(obj, segments) {
  let cur = obj;
  for (let i = 0; i < segments.length - 1; i++) {
    const seg = segments[i];
    if (cur[seg] === undefined) return false;
    cur = cur[seg];
  }
  const last = segments[segments.length - 1];
  if (!(last in cur)) return false;
  delete cur[last];
  return true;
}

Request Routing

All requests arrive at the same base URL. The server determines what to do from the HTTP method and the query parameters.

GET ?paths= — batched read

Parses the paths JSON array, resolves each path against the store, and returns a flat response object. Paths that do not exist — or that fail the authorization check — are set to null rather than being omitted.
GET /api?paths=["posts.page1","meta.title","users.user1"]
{
  "posts.page1": { "title": "Hello World", "body": "First post body." },
  "meta.title": "dragonJSON Demo",
  "users.user1": null
}

GET ?path=&command= — freeform query

Parses the command JSON object and passes it to handleCommand along with the data at path. The result is returned directly — it is not cached by the client.
GET /api?path=posts&command={"action":"paginate","cursor":0,"limit":10}
See Built-in $get commands for the available actions.

GET ?path=&hierarchical=true&target= — hierarchical batch

Walks the store from the root down to target, populating a __batch envelope with the value at every intermediate path. Used when enableHierarchicalBatch: true is set on the client.
GET /api?path=posts&target=posts.page1.title&hierarchical=true
{
  "__batch": {
    "posts": { "page1": { "title": "Hello World", "body": "..." }, "page2": { ... } },
    "posts.page1": { "title": "Hello World", "body": "First post body." },
    "posts.page1.title": "Hello World"
  }
}

GET ?path= — single-path read

The simplest case. Looks up the value at path in the store and returns it directly, or 404 if nothing exists there.
GET /api?path=posts.page1
{ "title": "Hello World", "body": "First post body." }

POST ?path= — mutation

Reads the request body, determines the operation from body.__op, checks authorization, and dispatches to the appropriate handler:
body.__opOperationHandler
(absent)$setWrites body to path in the store
"add"$addAppends body.value under path, using body.key if provided
"remove"$removeDeletes the node at path
All three return { "invalidate": [...] }. See the protocol spec for the full response contract.

Built-in $get Commands

The handleCommand function in server.js provides two built-in actions. Pass them as the command parameter from the client using .$get().

paginate

Slices an object or array and returns a page of results with cursor-based pagination. Command shape:
{ "action": "paginate", "cursor": 0, "limit": 10 }
Response shape:
{ "items": { ... }, "nextCursor": 10, "total": 42 }
nextCursor is null when there are no more pages. Example:
const page = await server.posts.$get({ action: "paginate", cursor: 0, limit: 10 });
// { items: { page1: { ... }, page2: { ... } }, nextCursor: null, total: 2 }

Performs a case-insensitive full-text filter over the string-serialised values of an object. Returns an array of matching entries, each with a key property prepended. Command shape:
{ "action": "search", "query": "hello" }
Response shape:
[{ "key": "page1", "title": "Hello World", "body": "First post body." }]
Example:
const results = await server.posts.$get({ action: "search", query: "Hello" });
// [{ key: "page1", title: "Hello World", body: "First post body." }]

Unknown commands

If the action is not recognised, handleCommand returns the data at path unchanged. You can add custom commands before the built-in handlers (see below).

Adding Custom Commands

The handleCommand function is where you extend the server with your own $get actions. Add your handlers at the top of the function, before the built-in paginate and search checks:
function handleCommand(pathStr, data, command) {
  // Add your own action handlers here
  if (command.action === "my-action") {
    return { result: "custom" };
  }

  // --- built-in handlers below ---

  if (command.action === "paginate") {
    // ...
  }

  if (command.action === "search") {
    // ...
  }

  // Unknown command — return data unchanged
  return data;
}
pathStr is available in handleCommand so you can branch on the path as well as the command action — useful when the same action name means different things at different paths.
Custom commands follow the same authorization rules as any other get:command operation. Configure access in authConfig using the "get:command" operation string — see Server Auth for details.

Build docs developers (and LLMs) love