Zum Inhalt springen

Lesen und suchen

Lesen geht per GET auf eine Sammlung. Filtern, Sortieren, Blättern und Auflösen hängst du als Query-Parameter an. Basis: GET /api/v1/_/<namespace>/<collection>.

Liste mit Filter, Sortierung und Blättern
GET /api/v1/_/<namespace>/content?filter={"lang":"de"}&sort=-name&limit=20&offset=0

Liste mit filter, sort, limit, offset.

Parameter Format Beispiel
filter MongoDB-Filter als JSON ?filter={"lang":"de","active":true}
sort Feld, - heißt absteigend; Komma oder wiederholt ?sort=-name, ?sort=type&sort=-language
limit Zahl, max. Einträge ?limit=20
offset Zahl, übersprungene Einträge ?offset=40
count 1 oder true ?count=1

Filter-Operatoren wie $and, $or, $in, $lte, $gte, $exists funktionieren. Der Starter nutzt sie selbst im publishedFilter (api/hooks/config.js): active: true plus Veröffentlichungsfenster publication.from/publication.to.

Nächste Seite, neueste zuerst
# nächste Seite, neueste zuerst (Annahme: Feld updatedAt existiert)
GET …/comment?filter={"contentId":"<id>"}&sort=-updatedAt&limit=10&offset=10

select begrenzt die Felder — Komma-getrennt oder wiederholt. Das spart Bandbreite und Kontext, vor allem bei Dateien.

Felder per select begrenzen
GET …/medialib?select=title,alt,tags
GET …/content?select=name&select=path&select=lang
  • select ohne file.data abfragen: Datei-Inhalte (file.data) sind groß — file.src reicht für Anzeige.
  • Der Starter-Hinweis dazu: Liste mit select ohne file.data lesen, sonst bläht der Kontext auf.
  • projection wählt eine vordefinierte Projektion aus der Sammlungs-YAML (projections-Block).
  • Unbekannte Felder oder fremde Projektionen → 400.

lookup löst Fremdschlüssel auf und legt das Ergebnis unter _lookup ab. Format: feld:sammlung, Rest mit Doppelpunkten (feld:sammlung:fremdfeld:projektion:select:als).

Verweise per lookup auflösen
# Navigation mit Seitendaten (Feld page -> content)
GET …/navigation?lookup=elements.page:content
# Content mit Hero- und Richtext-Bildern (Felder aus content.yml)
GET …/content?lookup=blocks.heroImage.image:medialib,blocks.image:medialib
  • Ohne lookup steht im Feld nur die ID (z. B. page: "<content.id>").
  • Mit lookup steht der Zieldatensatz in _lookup.page (Name, Pfad usw.).
  • Die App baut daraus den Link: href aus _lookup.page.path plus hash.
  • Mehrere Lookups per Komma oder wiederholtem ?lookup=.
  • Ziel ohne Leserecht oder unbekannte Sammlung → Fehler (lookup permission denied, Ziel nicht gefunden).
Endpunkt Wirkung Beispiel
GET …/<collection>/_count?filter={…} Nur Anzahl, keine Einträge GET …/comment/_count?filter={"contentId":"<id>"}
GET …/<collection>/_count?estimate=1 Schnelle Schätzung statt exakter Zahl (nur ohne filter) GET …/comment/_count?estimate=1
GET …/<collection>/_facet?facet=<feld> Werte zählen je Feld GET …/comment/_facet?facet=contentId
…/_facet?facet=<feld>&limit=10 Nur Top N GET …/comment/_facet?facet=contentId&limit=10
…/_facet?facet=<feld>&includeEmpty=1 Leere Werte mitzählen GET …/comment/_facet?facet=tags&includeEmpty=1
…/_facet?facet=<feld>&q=<text>&qName=<block> Werte der Treffer zählen GET …/productVariant/_facet?facet=brand&q=warme jacke&qName=combined

Die Kommentar-Anzahl je Seite (commentCount) kommt aus so einer Abfrage über contentId.

q und qName gelten auch für _facet: Die Zählung bezieht sich dann auf die Trefferliste, nicht auf die ganze Sammlung. Damit passt sich eine Filterseitenleiste der Suche an.

Parameter Bedeutung
q, qName Suchtext und Suchblock; gezählt werden nur noch die Treffer
qLimit Zahl, 0 oder all: wie viele Treffer die Zählung abdeckt
Facetten zur Suche
GET …/productVariant/_facet?facet=catalogProjection.brand&q=warme jacke&qName=combined
GET …/productVariant/_facet?facet=catalogProjection.brand&q=warme jacke&qName=combined&qLimit=all
  • Ohne qLimit ist das Fenster so groß wie rrf.topK des Suchblocks (ohne Angabe 500, siehe Suche) — also genau die Menge, die die Liste zeigt.
  • qLimit=all (oder qLimit=0) zählt alle Treffer; der Server deckelt bei 20 000 IDs. qLimit=<zahl> setzt das Fenster selbst.
  • Ein größeres Fenster als rrf.topK liefert bewusst mehr Treffer als die Liste zeigt: die Summe der Facettenwerte ist dann größer als die Trefferzahl der Liste.
  • Bei den filterbasierten Modi (text, regex, ngram, eval, filter) wird qLimit ignoriert — dort zählt der Filter ohnehin exakt.
  • Nur bei den Modi vector und combined ergänzt die Antwort einen Block search, z. B. {"mode":"combined","resolved":500,"limit":500,"truncated":true}. truncated: true heißt: Fenster voll, weitere Treffer sind möglich — auch wenn es exakt aufgeht, denn der Server holt kein Extra-Dokument.
  • Die Modi text, regex, ngram, eval und filter setzen q als MongoDB-Filter um. Dort ist die Zählung exakt und es gibt keinen search-Block.
  • Ohne Treffer kommt eine leere Facetten-Antwort ohne Datenbankzugriff.

Aggregate: Rückwärts zählen und rechnen (Listen-Route!)

Abschnitt betitelt „Aggregate: Rückwärts zählen und rechnen (Listen-Route!)“

aggregate hängt an der normalen GET-Liste — kein eigener Endpunkt wie _count/_facet. Es zählt oder verrechnet Dokumente einer anderen Sammlung, die auf deine Listeneinträge zeigen, und legt das Ergebnis unter _aggregate ab.

Kommentar-Anzahl je Content (Rückwärtszählung)
GET …/content?aggregate=comment:contentId:count:commentCount
# Antwort je Eintrag: {"_id": "…", "name": "…", "_aggregate": {"commentCount": 3}}
Baustein (ziel:feld:op:wert:als) Bedeutung Beispiel
ziel (Pflicht) Ziel-Sammlung, in der gezählt wird comment
feld (Pflicht) Feld dort, das auf deinen Eintrag zeigt contentId (dein _id ist die Quelle)
op count (Standard), exists, sum, avg, min, max count
wert Nur für sum/avg/min/max Pflicht: Zahlenfeld im Ziel (number, float, int, any) price
als Ergebnis-Schlüssel unter _aggregate; ohne Angabe erzeugt der Server einen (commentCount, commentPriceSum, …) commentCount

Formate: Kurzform mit Doppelpunkten, mehrere per Komma (?aggregate=a:x:count,b:y:count), JSON-Objekt/Array, wiederholtes ?aggregate= oder ?aggregate[]=. Nur lesend: Quell-Feld, Ziel-Feld (foreignField), valueField und Filter-Felder müssen für deine Rolle lesbar sein — sonst 400. Fehlt dir das get-Recht auf der Ziel-Sammlung, kommt 403 (aggregate permission denied). Unbekannte Sammlung, unbekanntes Feld oder unbekannte Op → 400; mehrere Specs mit gleichem Ergebnis-Schlüssel → 400 (aggregate result key conflict).

Leere Treffer haben feste Werte: count → 0, exists → false, sum → 0, avg/min/max → null.

q sucht, qName wählt den Suchindex aus dem search-Block der Sammlung. Die vollständige Index-Tabelle (Modi, Gewichte, Aufbau) steht unter Suche — hier nur die Abfrage:

Suche mit q und qName
# kombinierte Suche in content (RRF aus semantisch + fuzzy)
GET …/content?q=kontakt&qName=combined
# einfache Suche in Tags (Default-Index, regex)
GET …/tag?q=hero

?qExplain=1 erklärt, wie die Suche gewichtet hat — nützlich beim Debuggen.

Antwort Ursache Lösung
400 Ungültiges filter-JSON, unbekanntes select-Feld, falsche lookup-Syntax JSON prüfen, Feldnamen gegen die Sammlungs-YAML halten
permission denied Kein get-Recht für deine Rolle Rechte und Tokens prüfen
invalid projection projection heißt anders als in der YAML Namen im projections-Block nachschlagen
Leere Liste trotz Daten filter_public-Hook filtert active:true + Zeitraum (Rechte und Tokens) active:true setzen, publication-Fenster prüfen
_lookup leer Zieldatensatz nicht lesbar (meist active:true fehlt, siehe Rechte und Tokens) Zieldatensatz prüfen, nicht die Abfrage

Verifiziert am e3f92eb 2026-10-17 (tibi-svelte-starter), am 68b4278 2026-10-17 (tibi-server, collection_get/select/sort/facet/count/lookup), am 68b4278 2026-10-17 (tibi-server, aggregate: dbapi/aggregate.go, controller/collection_aggregate.go), am d0d699d 2026-10-20 (tibi-server: collection_facet.go, collection_facet_search.go, rrf.topK-Default)