Lesedauer 7 Minuten

Du kennst SQL, schreibst JOINs im Schlaf und weißt, wofür eine Zwischentabelle gut ist? Dann wirst du dich in PocketBase schnell zurechtfinden. Nur sieht vieles auf den ersten Blick anders aus: Es gibt kein JOIN-Statement in der API, keine Fremdschlüssel-Spalten mit _id-Suffix und meistens auch keine Zwischentabellen. Stattdessen arbeitest du mit Relationsfeldern, dem Parameter expand und sogenannten Back-Relations.

In diesem Beitrag übersetze ich dein SQL-Wissen Schritt für Schritt in die PocketBase-Welt. Als roten Faden nehme ich einen kleinen Blog mit Nutzern, Beiträgen, Tags und Kommentaren.

Das Grundprinzip: Relationsfeld statt Fremdschlüssel

Unter der Haube nutzt PocketBase SQLite. Eine Collection ist im Kern eine Tabelle, ein Record eine Zeile. Eine Relation legst du an, indem du einer Collection ein Feld vom Typ Relation hinzufügst und die Ziel-Collection auswählst. In diesem Feld speichert PocketBase die ID des verknüpften Records, also genau das, was in SQL dein Fremdschlüssel wäre.

Der wichtigste Unterschied: Ein Relationsfeld kann entweder Single sein (genau eine ID, gespeichert als String) oder Multiple (mehrere IDs, gespeichert als JSON-Array). Damit deckst du 1:n und n:m ab, ohne zusätzliche Tabellen anzulegen.

In der folgenden Übersicht habe ich dir zusammengestellt, welches SQL-Konzept welchem PocketBase-Konzept entspricht:

SQL-Konzepte und ihre Entsprechung in PocketBase

Mein Beispiel: ein kleiner Blog

Für alle folgenden Beispiele verwende ich vier Collections:

  • users: die eingebaute Auth-Collection mit name und email
  • posts: Beiträge mit title, einem Autor (author, Single-Relation auf users) und Tags (tags, Multiple-Relation auf tags)
  • tags: Schlagwörter mit name
  • comments: Kommentare mit text, dem zugehörigen Beitrag (post) und dem Verfasser (author)
Datenmodell des Beispiel-Blogs

1:n: Der klassische Fremdschlüssel

Ein Beitrag hat genau einen Autor, ein Autor kann viele Beiträge schreiben. In SQL würdest du das so modellieren:

In PocketBase legst du in der Collection posts ein Feld author vom Typ Relation an, wählst users als Ziel-Collection und stellst es auf Single. Ein Record sieht dann so aus:

Im Feld author steht nur die ID. Das ist exakt dein author_id, nur ohne Suffix.

Der JOIN heißt hier expand

In SQL holst du dir die Autordaten per JOIN:

In PocketBase hängst du stattdessen den Parameter expand an die Anfrage:

Mit dem JavaScript-SDK sieht das so aus:

Die Antwort enthält den Autor dann vollständig unter dem Schlüssel expand:

Anders als bei einem SQL-JOIN werden die Daten nicht in eine flache Ergebniszeile gemischt, sondern bleiben verschachtelt. Ich finde das für die Arbeit im Frontend meist sogar angenehmer.

Filtern über die Relation

Du willst nur die Beiträge von Anna? In SQL wäre das ein JOIN mit WHERE. In PocketBase greifst du im Filter einfach per Punkt-Notation auf Felder der verknüpften Collection zu:

PocketBase baut den nötigen JOIN intern selbst. Das funktioniert übrigens auch beim Sortieren, zum Beispiel mit sort: 'author.name'.

Die Rückrichtung: Back-Relations

Jetzt wird es aus meiner Sicht richtig spannend. Kommentare verweisen über das Feld post auf einen Beitrag. Was aber, wenn du einen Beitrag mitsamt seinen Kommentaren laden willst? Der Beitrag selbst hat ja kein Feld, das auf die Kommentare zeigt.

In SQL drehst du einfach den JOIN um:

PocketBase löst das mit der Syntax collection_via_feld. Gemeint ist: „alle Records aus comments, deren Feld post auf diesen Record zeigt“.

In post.expand.comments_via_post findest du anschließend ein Array mit allen Kommentaren. Auch im Filter kannst du Back-Relations nutzen. So findest du alle Beiträge, in denen irgendein Kommentar das Wort „SQLite“ enthält:

Das Fragezeichen vor dem Operator bedeutet „mindestens einer“. Es entspricht also einem EXISTS bzw. ANY in SQL. Ohne Fragezeichen müssten alle verknüpften Kommentare die Bedingung erfüllen.

Mein Tipp: Beim Expand einer Back-Relation liefert PocketBase nicht beliebig viele Records mit. Wenn ein Beitrag tausende Kommentare haben kann, rate ich dir, die Kommentare direkt und paginiert abzufragen, etwa mit filter: 'post = "p1x9k2m4n6q8r0s"' auf der Collection comments.

n:m: Multiple-Relation statt Zwischentabelle

Ein Beitrag hat mehrere Tags, ein Tag gehört zu vielen Beiträgen. In SQL baust du dafür eine Zwischentabelle:

In PocketBase brauchst du das in den meisten Fällen nicht. Du legst in posts ein Relationsfeld tags an und stellst es auf Multiple (also eine maximale Auswahl größer als 1). PocketBase speichert dann ein Array von IDs direkt im Record:

n:m-Beziehung in SQL und in PocketBase

Abfragen funktionieren genauso wie bei Single-Relationen. expand: 'tags' liefert dir ein Array mit allen Tag-Records. Für den Filter verwendest du wieder den „mindestens einer“-Operator:

Beim Schreiben musst du nicht das komplette Array neu senden. Mit den Modifikatoren + und - fügst du einzelne IDs hinzu oder entfernst sie, ähnlich einem INSERT bzw. DELETE auf der Zwischentabelle:

Wann du doch eine Zwischen-Collection brauchst

Sobald die Verknüpfung selbst Daten trägt, reicht ein Array von IDs nicht mehr. Typisches Beispiel: Ein Nutzer ist Mitglied in mehreren Teams und hat in jedem Team eine eigene Rolle. Dann legst du wie in SQL eine eigene Collection memberships an, mit den Feldern user (Relation auf users), team (Relation auf teams) und role (Text oder Select). Mit einem Unique-Index auf user und team verhinderst du doppelte Einträge.

1:1: Single-Relation mit Unique-Index

Für eine echte 1:1-Beziehung, etwa ein Profil pro Nutzer, nimmst du eine Single-Relation und machst sie eindeutig. In den Einstellungen der Collection kannst du dafür einen Index anlegen, und zwar in ganz normaler SQL-Syntax:

Damit kann jeder Nutzer höchstens ein Profil haben, genau wie mit einer UNIQUE-Constraint auf dem Fremdschlüssel.

Verschachteltes Expand: mehrere JOINs auf einmal

Was in SQL eine Kette von JOINs ist, erledigst du in PocketBase mit Punkt-Notation im expand. Mehrere Relationen trennst du mit Kommas. Ein Kommentar mit Beitrag, dessen Autor und dessen Tags:

PocketBase erlaubt dabei mehrere Ebenen Verschachtelung. Wenn du nicht alle Felder brauchst, schränkst du die Antwort mit dem Parameter fields ein, ähnlich wie du in SQL statt SELECT * gezielt Spalten auswählst:

Löschen: Cascade Delete und referentielle Integrität

In SQL legst du mit ON DELETE CASCADE fest, dass abhängige Zeilen mitgelöscht werden. PocketBase bietet dafür an jedem Relationsfeld die Option Cascade delete.

  • Cascade delete aktiv: Löschst du einen Beitrag, verschwinden auch alle Kommentare, deren Feld post auf ihn zeigt. Für das Feld comments.post in meinem Beispiel halte ich das für sinnvoll.
  • Cascade delete inaktiv: PocketBase entfernt beim Löschen die Referenz aus dem Relationsfeld, entspricht also grob ON DELETE SET NULL. Ist das Feld jedoch ein Pflichtfeld, verweigert PocketBase das Löschen, ähnlich wie ON DELETE RESTRICT.

Bei posts.author würde ich Cascade delete dagegen nicht aktivieren: Ein gelöschter Nutzer würde sonst all seine Beiträge mitnehmen. Ich empfehle dir deshalb, pro Feld zu überlegen, welches Verhalten fachlich richtig ist.

Relationen in API Rules nutzen

Den größten Vorteil von Relationsfeldern sehe ich bei den Zugriffsregeln. Weil du per Punkt-Notation durch Relationen navigieren kannst, lassen sich Berechtigungen sehr ausdrucksstark formulieren. Beispiel: Nur der Autor eines Beitrags darf dessen Kommentare löschen:

In SQL würdest du dafür eine Row-Level-Security-Policy mit Subquery schreiben. In PocketBase ist es eine Zeile.

Typische Stolperfallen für SQL-Profis

  • Expand respektiert Zugriffsregeln: Darf der aktuelle Nutzer die verknüpfte Collection nicht lesen, bleibt expand einfach leer. Es gibt keinen Fehler. Wenn ein Expand „nicht funktioniert“, prüfe zuerst die View Rule der Ziel-Collection.
  • Keine freien JOINs über die API: Du kannst nur entlang definierter Relationsfelder expandieren. Für Bedingungen über nicht verknüpfte Collections gibt es in Filtern und Rules die Syntax @collection.name.feld.
  • Kein GROUP BY in der Records-API: Für Aggregationen wie Anzahl der Kommentare pro Beitrag nutzt du eine View Collection. Die definierst du mit einem ganz normalen SQL-SELECT, inklusive JOIN, COUNT und GROUP BY. Sie ist allerdings nur lesbar.
  • Single und Multiple sind unterschiedliche Datentypen: Single liefert einen String, Multiple ein Array. Stellst du ein bestehendes Feld später um, muss dein Frontend damit umgehen können.

Fazit

Wenn du SQL kannst, kennst du die Konzepte hinter PocketBase-Relationen bereits. Der Fremdschlüssel heißt hier Relationsfeld, der JOIN heißt expand, und der umgekehrte JOIN heißt Back-Relation mit _via_. Neu ist vor allem, dass n:m-Beziehungen meist ohne Zwischentabelle auskommen und dass Filter und API Rules ganz selbstverständlich durch Relationen navigieren. Mein Rat: Lege dir den Beispiel-Blog selbst an und probiere die Abfragen im Admin-Dashboard und mit dem SDK aus.

Wenn du tiefer einsteigen willst, empfehle ich dir die offizielle Dokumentation unter Working with relations sowie in der Records API.

Häufige Fragen zu Relationen in PocketBase

Gibt es in PocketBase echte Fremdschlüssel wie in SQL?

Ein Relationsfeld speichert die ID des Ziel-Records und PocketBase prüft beim Speichern, ob diese ID existiert. Das Verhalten beim Löschen steuerst du über die Option Cascade delete. Funktional entspricht das einem Fremdschlüssel mit definierter Löschregel.

Wie mache ich einen JOIN in PocketBase?

Über den Parameter expand, zum Beispiel ?expand=author. Die verknüpften Records landen verschachtelt im Schlüssel expand der Antwort. Mehrere Relationen trennst du mit Kommas, tiefere Ebenen erreichst du per Punkt-Notation.

Was bedeutet comments_via_post?

Das ist eine Back-Relation. Sie liefert alle Records der Collection comments, deren Feld post auf den aktuellen Record zeigt. Du kannst sie in expand, in Filtern und in API Rules verwenden.

Brauche ich für n:m-Beziehungen eine Zwischentabelle?

Meist nicht. Ein Relationsfeld mit Mehrfachauswahl speichert mehrere IDs direkt im Record. Nur wenn die Verknüpfung eigene Daten wie eine Rolle oder ein Datum braucht, legst du eine eigene Zwischen-Collection an.

Warum ist mein expand leer?

In den meisten Fällen fehlt die Leseberechtigung auf der Ziel-Collection. Expand berücksichtigt die View Rule der verknüpften Collection. Prüfe außerdem, ob der Feldname im expand-Parameter exakt stimmt.

Kann ich in PocketBase Aggregationen wie COUNT oder GROUP BY nutzen?

Nicht direkt über die Records-API. Dafür gibt es View Collections, die du mit einer SQL-SELECT-Abfrage definierst. Das Ergebnis kannst du anschließend wie eine normale Collection lesen und filtern.