<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>HTTP API on back(d)</title><link>https://fernandezvara.github.io/backd/docs/build/api/</link><description>Recent content in HTTP API on back(d)</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://fernandezvara.github.io/backd/docs/build/api/index.xml" rel="self" type="application/rss+xml"/><item><title>Documents</title><link>https://fernandezvara.github.io/backd/docs/build/api/documents/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/documents/</guid><description>&lt;p&gt;Every configured collection gets the same set of endpoints:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Method&lt;/th&gt;
 &lt;th&gt;Path&lt;/th&gt;
 &lt;th&gt;Behavior&lt;/th&gt;
 &lt;th&gt;Success&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Create a document&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;201&lt;/code&gt; + &lt;code&gt;Location&lt;/code&gt; + &lt;code&gt;ETag&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;List documents&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}/{id}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Fetch one document&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt; + &lt;code&gt;ETag&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}/{id}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Replace the whole document&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt; + &lt;code&gt;ETag&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;PATCH&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}/{id}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Partial update (&lt;a href="https://www.rfc-editor.org/rfc/rfc7396"&gt;JSON Merge Patch&lt;/a&gt;)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt; + &lt;code&gt;ETag&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/{collection}/{id}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Delete the document&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;204&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/{realm}/{database}/_batch&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="#batch-writes"&gt;Create, replace, patch and delete atomically&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;An unknown realm, database, collection or &lt;code&gt;id&lt;/code&gt; returns &lt;code&gt;404 not_found&lt;/code&gt;. PUT and PATCH never create documents: there are no upserts, so create with POST and let the server choose the &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In a realm with &lt;code&gt;auth: enabled&lt;/code&gt;, requests are decided by the collection&amp;rsquo;s &lt;a href="../../auth/rules/"&gt;access rules&lt;/a&gt;, or allowed outright with an &lt;a href="../../auth/api-keys/"&gt;API key&lt;/a&gt; in &lt;code&gt;Authorization: Bearer …&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Request bodies must be a single JSON object. Anything else returns &lt;code&gt;400 invalid_json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="document-shape"&gt;Document shape&lt;a class="hd-anchor" href="#document-shape" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Documents are returned as bare JSON objects:&lt;/p&gt;</description></item><item><title>Querying</title><link>https://fernandezvara.github.io/backd/docs/build/api/querying/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/querying/</guid><description>&lt;p&gt;The list endpoint, &lt;code&gt;GET /v1/{realm}/{database}/{collection}&lt;/code&gt;, accepts these query parameters:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Parameter&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;where&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;td&gt;JSON object with filter conditions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;order_by&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;_meta.created_at,id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Comma-separated fields; prefix a field with &lt;code&gt;-&lt;/code&gt; for descending&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;limit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Page size, &lt;code&gt;1&lt;/code&gt;–&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;skip&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Number of documents to skip&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;after&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;The &lt;code&gt;next_cursor&lt;/code&gt; of the previous page: continues the list after it (see &lt;a href="#paging-with-a-cursor"&gt;Paging with a cursor&lt;/a&gt;). Can&amp;rsquo;t be combined with &lt;code&gt;skip&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt; adds &lt;code&gt;total&lt;/code&gt;, the number of documents matching &lt;code&gt;where&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;deleted&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;In a collection with &lt;a href="../documents/#soft-delete"&gt;soft delete&lt;/a&gt;: &lt;code&gt;only&lt;/code&gt; lists the deleted documents (the trash), &lt;code&gt;include&lt;/code&gt; both. Needs the &lt;code&gt;restore&lt;/code&gt; rule besides &lt;code&gt;read&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each parameter may appear at most once. Any other parameter returns &lt;code&gt;400 invalid_query&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Aggregations</title><link>https://fernandezvara.github.io/backd/docs/build/api/aggregations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/aggregations/</guid><description>&lt;p&gt;A dashboard, a counter or a report needs summaries: orders per status, revenue per month, how many people did something. Without help that is a function per summary, or every document fetched and added up in the browser. &lt;code&gt;GET …/_aggregate&lt;/code&gt; does it in the database, over &lt;strong&gt;only the documents the caller may read&lt;/strong&gt;, with a fixed pipeline: the client picks &lt;em&gt;declared&lt;/em&gt; fields, buckets and metrics, never database syntax.&lt;/p&gt;
&lt;h2 id="declare-what-can-be-summarized"&gt;Declare what can be summarized&lt;a class="hd-anchor" href="#declare-what-can-be-summarized" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A collection is opt-in: &lt;a href="../../configuration/config-dir/#aggregations"&gt;&lt;code&gt;aggregate:&lt;/code&gt; in &lt;code&gt;collection.yaml&lt;/code&gt;&lt;/a&gt; lists the fields a request may group by and measure. A collection without it answers &lt;code&gt;400 aggregate_not_enabled&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Full-text search</title><link>https://fernandezvara.github.io/backd/docs/build/api/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/search/</guid><description>&lt;p&gt;&lt;code&gt;$contains&lt;/code&gt; looks for a substring and reads every document. &lt;strong&gt;Full-text search&lt;/strong&gt; looks for &lt;em&gt;words&lt;/em&gt;, through an index: it finds &lt;code&gt;running&lt;/code&gt; in &amp;ldquo;He was running late&amp;rdquo; and &amp;ldquo;runs&amp;rdquo;, ranks documents by how well they match, and stays fast on collections that &lt;code&gt;$contains&lt;/code&gt; would crawl through. It is declared per collection, in &lt;code&gt;indexes.json&lt;/code&gt;, and used with &lt;code&gt;$search&lt;/code&gt; in &lt;code&gt;where&lt;/code&gt;: the &lt;a href="../querying/#full-text-search"&gt;reference&lt;/a&gt; has the request; this page is how to use it well.&lt;/p&gt;
&lt;h2 id="declare-the-index"&gt;Declare the index&lt;a class="hd-anchor" href="#declare-the-index" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;fields&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;default_language&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;english&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;language_field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lang&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pick the fields people search.&lt;/strong&gt; Titles, names, tags, summaries and bodies; not ids, codes or numbers (use &lt;code&gt;where&lt;/code&gt; on those). Fields are strings or arrays of strings, up to 30.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weights say what matters.&lt;/strong&gt; A word in a weight-10 title counts ten times a word in a weight-1 body, so &amp;ldquo;handbook&amp;rdquo; in a title beats &amp;ldquo;handbook&amp;rdquo; mentioned in passing. Start with 10 / 5 / 1 for title / keywords / body and adjust by looking at results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One index per collection&lt;/strong&gt;, a limit of the database: every searchable field goes in the same entry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Changing it rebuilds it.&lt;/strong&gt; Different fields, weights or languages replace the index at the next provisioning, with a warning in the log; searches answer &lt;code&gt;503 index_building&lt;/code&gt; meanwhile (with &lt;code&gt;Retry-After&lt;/code&gt;; the JavaScript client retries 503s when its &lt;code&gt;retry&lt;/code&gt; option is on). On a large collection do it at a quiet time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="languages-and-stemming"&gt;Languages and stemming&lt;a class="hd-anchor" href="#languages-and-stemming" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Stemming reduces words to a root, so &lt;code&gt;corriendo&lt;/code&gt; finds &lt;code&gt;correr&lt;/code&gt; and &lt;code&gt;shoes&lt;/code&gt; finds &lt;code&gt;shoe&lt;/code&gt;; stop words (&lt;code&gt;the&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;und&lt;/code&gt;) are ignored. Both depend on the language, in two places:&lt;/p&gt;</description></item><item><title>Caching and conditional GET</title><link>https://fernandezvara.github.io/backd/docs/build/api/caching/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/caching/</guid><description>&lt;p&gt;An app that reads the same documents again and again can ask the server &amp;ldquo;has this changed?&amp;rdquo; instead of downloading it again. &lt;code&gt;backd&lt;/code&gt; answers with &lt;code&gt;304 Not Modified&lt;/code&gt; and no body when nothing changed. This page explains what that guarantees, how to do it by hand, and how a collection can let a CDN serve its public reads.&lt;/p&gt;
&lt;h2 id="how-a-revalidation-works"&gt;How a revalidation works&lt;a class="hd-anchor" href="#how-a-revalidation-works" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;A read of a document or a list returns an &lt;code&gt;ETag&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The next read of the same URL sends it back in &lt;code&gt;If-None-Match&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If what the caller would receive is the same, the answer is &lt;code&gt;304&lt;/code&gt; with the same &lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;Cache-Control&lt;/code&gt; and &lt;code&gt;Vary&lt;/code&gt;, and no body. Otherwise it is a normal &lt;code&gt;200&lt;/code&gt; with the new &lt;code&gt;ETag&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /v1/shop/main/orders/ord_1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;→ 200 ETag: &amp;#34;3&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /v1/shop/main/orders/ord_1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;If-None-Match: &amp;#34;3&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;→ 304 ETag: &amp;#34;3&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="what-the-server-still-does"&gt;What the server still does&lt;a class="hd-anchor" href="#what-the-server-still-does" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;304&lt;/code&gt; saves the transfer, not the work. On every read &lt;code&gt;backd&lt;/code&gt; still runs the query and evaluates the &lt;a href="../../auth/rules/"&gt;read rule&lt;/a&gt; for the caller, and only then compares tags. That is why it is safe:&lt;/p&gt;</description></item><item><title>Realtime</title><link>https://fernandezvara.github.io/backd/docs/build/api/realtime/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/realtime/</guid><description>&lt;p&gt;A collection that declares &lt;a href="../../configuration/config-dir/#realtime"&gt;&lt;code&gt;realtime: true&lt;/code&gt;&lt;/a&gt; can be followed live. A client opens &lt;strong&gt;one stream&lt;/strong&gt; for the whole realm with &lt;code&gt;POST /v1/{realm}/_realtime&lt;/code&gt;, names what it follows, and receives the changes the caller may read as server-sent events.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST /v1/shop/_realtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;session or API key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;subscriptions&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; { &amp;#34;id&amp;#34;: &amp;#34;mine&amp;#34;, &amp;#34;db&amp;#34;: &amp;#34;main&amp;#34;, &amp;#34;collection&amp;#34;: &amp;#34;orders&amp;#34;, &amp;#34;where&amp;#34;: { &amp;#34;status&amp;#34;: &amp;#34;open&amp;#34; } },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; { &amp;#34;id&amp;#34;: &amp;#34;order&amp;#34;, &amp;#34;db&amp;#34;: &amp;#34;main&amp;#34;, &amp;#34;collection&amp;#34;: &amp;#34;orders&amp;#34;, &amp;#34;doc&amp;#34;: &amp;#34;o_17&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Read it with &lt;code&gt;fetch&lt;/code&gt; and a streaming body: &lt;code&gt;EventSource&lt;/code&gt; can&amp;rsquo;t send an &lt;code&gt;Authorization&lt;/code&gt; header, and sessions never go in URLs.&lt;/p&gt;</description></item><item><title>Your realm's OpenAPI</title><link>https://fernandezvara.github.io/backd/docs/build/api/openapi/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/openapi/</guid><description>&lt;p&gt;The &lt;a href="../#openapi-specification"&gt;contract&lt;/a&gt; describes backd&amp;rsquo;s generic API: documents are open objects there, because their fields come from your config. A realm&amp;rsquo;s &lt;strong&gt;generated document&lt;/strong&gt; is the same API with your config filled in: every collection with its schema, the sign-in methods the realm enables, its functions with their input and output. Generators of typed clients (openapi-generator, for Kotlin, Swift and many more) read it. Dart and Flutter apps use &lt;a href="../../clients/dart/"&gt;package:backd&lt;/a&gt; instead — a hand-written client that doesn&amp;rsquo;t come from a generator.&lt;/p&gt;</description></item><item><title>References and expansion</title><link>https://fernandezvara.github.io/backd/docs/build/api/references/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/build/api/references/</guid><description>&lt;p&gt;Almost every app has relations: a post&amp;rsquo;s author, an invoice&amp;rsquo;s customer, an item&amp;rsquo;s tags. Without help, each relation is another request and a join on the client. &lt;strong&gt;Reference expansion&lt;/strong&gt; returns the referenced documents next to the result, in the same request, with one batched query per path for the whole page, and with every expanded document passing &lt;strong&gt;its own collection&amp;rsquo;s rules&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Four principles hold everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declared and explicit.&lt;/strong&gt; Only references declared in &lt;code&gt;collection.yaml&lt;/code&gt; can be expanded, and only when the request asks. Nothing is expanded by default, and the &lt;a href="../../clients/js/#expanding-references"&gt;JavaScript client&lt;/a&gt; never adds it by itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Additive.&lt;/strong&gt; The document is exactly what &lt;code&gt;schema.json&lt;/code&gt; says; expansions go in &lt;code&gt;_expanded&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rules everywhere.&lt;/strong&gt; A referenced document is returned only if the caller could read it directly. Unreadable and missing look the same.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bounded, visible cost.&lt;/strong&gt; Limits on depth, paths, arrays and totals, and headers that say what a request cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="declaring-a-reference"&gt;Declaring a reference&lt;a class="hd-anchor" href="#declaring-a-reference" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# collection.yaml of &amp;#34;posts&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;references&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the field &amp;#34;category&amp;#34; holds the id of a categories document&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;categories&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tags&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;many&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the field is an array of ids&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max_expand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the owner&amp;#39;s profile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;_meta.owner&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;profiles&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;_meta.owner &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# profiles has a unique index on _meta.owner&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;customers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;crm &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# another database of the same realm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Key&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;collection&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;required&lt;/td&gt;
 &lt;td&gt;the collection the values point to&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;field&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the reference&amp;rsquo;s name&lt;/td&gt;
 &lt;td&gt;where the value is in this collection: a declared string field (an array of strings with &lt;code&gt;many&lt;/code&gt;), a dotted path of one, &lt;code&gt;_meta.owner&lt;/code&gt; or &lt;code&gt;_meta.created_by&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;database&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;this database&lt;/td&gt;
 &lt;td&gt;the database of the target; always in the same realm&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the field of the target that equals the value&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;many&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the source is an array of values&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_expand&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the instance&amp;rsquo;s value&lt;/td&gt;
 &lt;td&gt;follow at most this many ids of the array; only with &lt;code&gt;many&lt;/code&gt;, and only lower than &lt;code&gt;BACKD_EXPAND_MAX_ARRAY&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;How a value finds its document.&lt;/strong&gt; The value of &lt;code&gt;field&lt;/code&gt; is compared with &lt;code&gt;key&lt;/code&gt; in the target: first &lt;code&gt;id&lt;/code&gt; (the default), otherwise the field named by &lt;code&gt;key&lt;/code&gt;, which can be a declared field or &lt;code&gt;_meta.owner&lt;/code&gt; / &lt;code&gt;_meta.created_by&lt;/code&gt;. Each value must match at most one document, so a &lt;code&gt;key&lt;/code&gt; other than &lt;code&gt;id&lt;/code&gt; needs a &lt;strong&gt;unique index&lt;/strong&gt; of its own in the target&amp;rsquo;s &lt;code&gt;indexes.json&lt;/code&gt; (one field; soft-delete&amp;rsquo;s extra key doesn&amp;rsquo;t matter).&lt;/p&gt;</description></item></channel></rss>