Suche im CMS heißt sonst: Elasticsearch daneben stellen, synchron halten, bezahlen. RustyCMS bringt den Index mit. Jede Collection bekommt einen eigenen, er wird bei Writes mitgepflegt, liegt auf Platte und überlebt Neustarts. Für Frontend und Redaktion ist es ein Parameter.
Ein Parameter auf jeder Liste
_q funktioniert auf jedem List-Endpoint und liefert Treffer nach Relevanz (BM25). Die Syntax deckt das ab, was man tatsächlich tippt:
GET /api/content/blog_post?_q=hamburg # Wörter, UND-verknüpft
GET /api/content/blog_post?_q=%22exakte+phrase%22 # Phrase in Anführungszeichen
GET /api/content/blog_post?_q=hamb*&_locale=de # Präfix
GET /api/content/blog_post?_q=%2Bwind+-solar # Pflichtwort, Ausschluss
Groß- und Kleinschreibung spielt keine Rolle. Ist eine Query nicht parsebar, wird sie bereinigt und erneut versucht, statt einen Fehler zu liefern.
Über alle Collections auf einmal
GET /api/search?q=… sucht über alle Collections, die der Nutzer sehen darf, und gruppiert die Treffer pro Collection (limit_per_collection, Default 5). Das ist der Endpoint hinter Cmd+K in der Admin-UI — und ebenso gut die Basis für eine Site-Suche im Frontend.
Sprachbewusst
Lokalisierte Felder werden pro Sprache indexiert: ein deutscher Text landet im deutschen Zweig, ein englischer im englischen. Eine Anfrage mit _locale=de trifft deutsche Inhalte plus alles, was nicht lokalisiert ist — Tags etwa sollen in jeder Sprachsicht auffindbar bleiben.
Filter bleiben Filter
Volltext ist nur die erste Stufe. Status, Feldfilter, Pagination, _resolve und _fields laufen danach wie gewohnt; ein explizites _sort überschreibt die Relevanzreihenfolge. So kombinierst du „Suche nach windrad” mit „nur published, nur Kategorie energie, sortiert nach Datum” in einem Request.
Ein Index, um den sich niemand kümmert
Der erste _q-Request auf einer kalten Collection baut den Index aus dem Bestand — bei zehntausend Einträgen knapp eine Sekunde. Jeder Write aktualisiert ihn im Hintergrund, ohne den Schreibpfad zu blockieren. Nach einem Neustart wird er von Platte geöffnet; ist er leer, baut ihn der nächste Request neu. Das Auto-Backup lässt ihn bewusst aus — er regeneriert sich aus dem Content.
Im Postgres-Modus übernimmt die Datenbank selbst: Volltext als tsvector, geschrieben in derselben Transaktion wie der Eintrag, Stemming je Sprache — Windrad findet dort auch Windräder. Mehrere Instanzen teilen sich damit einen Index, der nie hinterherhinkt.
Bewusst nicht drin
Keine Hervorhebung des Treffer-Kontexts, kein Feld-Boosting (der Titel wiegt nicht mehr als der Body), im Postgres-Modus keine Tippfehler-Toleranz und keine Phrasen-/Operator-Syntax. Externe Collections laufen nicht durch den Index. Wer diese Dinge braucht, weiß es — für alle anderen ist die Suche einfach da.