[{"data":1,"prerenderedAt":1285},["Reactive",2],{"content-/blog/pocketbase":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"locale":10,"draft":6,"date":11,"image":12,"tags":15,"imageLogo":18,"imageLogo2":23,"seoImage":27,"body":29,"_type":1280,"_id":1281,"_source":1282,"_file":1283,"_extension":1284},"/blog/pocketbase","blog",false,"","PocketBase – Erfahrungen aus LingoDeck","Backend in einer einzigen Datei: moderne Oberfläche, einfaches Deployment und Anpassungsfähigkeit – aber auch kein SQL über die API, ein Pre-1.0-Stand und ein Admin-Panel, das sich nicht erweitern lässt","de","2026-09-08T00:00:00.000Z",{"src":13,"alt":14},"/background/background-6.svg","background",[16,17],"tools","nuxt",{"src":19,"alt":20,"width":21,"left":22},"/logo/pocketbase.svg","pocketbase-logo","25%","10%",{"src":24,"alt":25,"width":26,"right":22},"/logo/sqlite.svg","sqlite-logo","18%",{"src":28},"/pocketbase.jpg",{"type":30,"children":31,"toc":1262},"root",[32,61,68,73,144,158,167,173,180,193,246,251,257,262,333,338,387,400,405,410,459,471,477,482,588,616,628,634,639,679,708,713,744,750,755,761,767,786,821,833,839,851,1069,1074,1110,1122,1128,1133,1138,1144,1156,1161,1167,1224,1230,1235,1256],{"type":33,"tag":34,"props":35,"children":36},"element","p",{},[37,40,50,52,59],{"type":38,"value":39},"text","Ich habe ",{"type":33,"tag":41,"props":42,"children":47},"a",{"href":43,":target":44,"rel":45},"https://pocketbase.io/","\\_blank",[46],"nofollow",[48],{"type":38,"value":49},"PocketBase",{"type":38,"value":51}," für ",{"type":33,"tag":41,"props":53,"children":56},{"href":54,":target":44,"rel":55},"https://lingodeck.foxwebcraft.com",[46],[57],{"type":38,"value":58},"LingoDeck",{"type":38,"value":60}," verwendet, eine Karteikarten-App zum Sprachenlernen mit Spaced Repetition, KI-generierten Karten und Vorlesefunktion. Das Frontend ist eine Nuxt-4-App, als Backend läuft PocketBase 0.30 mit dem JavaScript-SDK. Zeit für ein ehrliches Fazit.",{"type":33,"tag":62,"props":63,"children":65},"h2",{"id":64},"was-ist-pocketbase",[66],{"type":38,"value":67},"Was ist PocketBase?",{"type":33,"tag":34,"props":69,"children":70},{},[71],{"type":38,"value":72},"PocketBase ist ein Open-Source-Backend (MIT-Lizenz), das aus einer einzigen ausführbaren Datei besteht. Sie ist in Go geschrieben und bringt alles Wichtige mit:",{"type":33,"tag":74,"props":75,"children":76},"ul",{},[77,91,102,112,122,134],{"type":33,"tag":78,"props":79,"children":80},"li",{},[81,83,89],{"type":38,"value":82},"eine ",{"type":33,"tag":84,"props":85,"children":86},"strong",{},[87],{"type":38,"value":88},"SQLite-Datenbank",{"type":38,"value":90},", die als Datei neben dem Programm liegt",{"type":33,"tag":78,"props":92,"children":93},{},[94,95,100],{"type":38,"value":82},{"type":33,"tag":84,"props":96,"children":97},{},[98],{"type":38,"value":99},"REST-API",{"type":38,"value":101}," für jede Collection, dazu Realtime-Abonnements",{"type":33,"tag":78,"props":103,"children":104},{},[105,110],{"type":33,"tag":84,"props":106,"children":107},{},[108],{"type":38,"value":109},"Authentifizierung",{"type":38,"value":111}," mit E-Mail und Passwort, OAuth2 und mehr",{"type":33,"tag":78,"props":113,"children":114},{},[115,120],{"type":33,"tag":84,"props":116,"children":117},{},[118],{"type":38,"value":119},"Dateiuploads",{"type":38,"value":121},", lokal oder in einem S3-Speicher",{"type":33,"tag":78,"props":123,"children":124},{},[125,127,132],{"type":38,"value":126},"ein ",{"type":33,"tag":84,"props":128,"children":129},{},[130],{"type":38,"value":131},"Admin-Panel",{"type":38,"value":133},", in dem Collections, Felder, Zugriffsregeln, Logs und Einstellungen verwaltet werden",{"type":33,"tag":78,"props":135,"children":136},{},[137,142],{"type":33,"tag":84,"props":138,"children":139},{},[140],{"type":38,"value":141},"Hooks und Migrationen",{"type":38,"value":143}," in JavaScript (alternativ lässt sich PocketBase als Go-Framework erweitern)",{"type":33,"tag":34,"props":145,"children":146},{},[147,149,156],{"type":38,"value":148},"Man startet die Datei mit ",{"type":33,"tag":150,"props":151,"children":153},"code",{"className":152},[],[154],{"type":38,"value":155},"./pocketbase serve",{"type":38,"value":157}," und hat ein fertiges Backend. Für das Frontend gibt es SDKs für JavaScript und Dart. In der App sprechen die Nuxt-Seiten und der Nuxt-Server über das JS-SDK mit PocketBase. Realtime und OAuth habe ich bisher nicht gebraucht.",{"type":33,"tag":34,"props":159,"children":160},{},[161],{"type":33,"tag":162,"props":163,"children":166},"img",{"alt":164,"src":165},"Das PocketBase-Admin-Panel mit der Collection „cards“","/images/blog/pocketbase.webp",[],{"type":33,"tag":62,"props":168,"children":170},{"id":169},"vorteile",[171],{"type":38,"value":172},"Vorteile",{"type":33,"tag":174,"props":175,"children":177},"h3",{"id":176},"moderne-oberfläche",[178],{"type":38,"value":179},"Moderne Oberfläche",{"type":33,"tag":34,"props":181,"children":182},{},[183,185,191],{"type":38,"value":184},"Das Admin-Panel ist schnell, aufgeräumt und für ein Backend, das man in einer Minute installiert hat, erstaunlich vollständig. Auf dem Screenshot sieht man die Collection ",{"type":33,"tag":150,"props":186,"children":188},{"className":187},[],[189],{"type":38,"value":190},"cards",{"type":38,"value":192}," mit Suche und Filter. Auch sonst ist alles an einem Ort:",{"type":33,"tag":74,"props":194,"children":195},{},[196,206,216,226,236],{"type":33,"tag":78,"props":197,"children":198},{},[199,204],{"type":33,"tag":84,"props":200,"children":201},{},[202],{"type":38,"value":203},"Collections und Felder",{"type":38,"value":205}," mit Typen wie Text, Zahl, Select, Relation, JSON, Datei und Autodate",{"type":33,"tag":78,"props":207,"children":208},{},[209,214],{"type":33,"tag":84,"props":210,"children":211},{},[212],{"type":38,"value":213},"Zugriffsregeln",{"type":38,"value":215}," als Ausdruck pro Aktion (List, View, Create, Update, Delete), mit Editor und Autovervollständigung",{"type":33,"tag":78,"props":217,"children":218},{},[219,224],{"type":33,"tag":84,"props":220,"children":221},{},[222],{"type":38,"value":223},"API Preview",{"type":38,"value":225},", die für jede Collection fertige Beispielanfragen zeigt",{"type":33,"tag":78,"props":227,"children":228},{},[229,234],{"type":33,"tag":84,"props":230,"children":231},{},[232],{"type":38,"value":233},"Logs und Statistiken",{"type":38,"value":235}," der Anfragen",{"type":33,"tag":78,"props":237,"children":238},{},[239,244],{"type":33,"tag":84,"props":240,"children":241},{},[242],{"type":38,"value":243},"Einstellungen",{"type":38,"value":245}," für Mailversand, Dateispeicher und automatische Backups",{"type":33,"tag":34,"props":247,"children":248},{},[249],{"type":38,"value":250},"Die Oberfläche richtet sich allerdings an Entwickler. Für Redakteure, die Texte pflegen, ist sie kein Ersatz für ein klassisches CMS. Dazu später mehr.",{"type":33,"tag":174,"props":252,"children":254},{"id":253},"anpassungsfähigkeit",[255],{"type":38,"value":256},"Anpassungsfähigkeit",{"type":33,"tag":34,"props":258,"children":259},{},[260],{"type":38,"value":261},"Das Datenmodell ist frei, und die Zugriffsregeln sind mächtiger, als sie auf den ersten Blick aussehen. Jede Collection hat fünf Regeln (List, View, Create, Update, Delete), und jede davon ist ein kurzer Ausdruck. Nutzer dürfen zum Beispiel nur ihre eigenen Datensätze sehen und anlegen:",{"type":33,"tag":263,"props":264,"children":268},"pre",{"className":265,"code":266,"language":267,"meta":7,"style":7},"language-js shiki shiki-themes dracula","notes.listRule   = 'owner = @request.auth.id'\nnotes.createRule = '@request.auth.id != \"\" && owner = @request.auth.id'\n","js",[269],{"type":33,"tag":150,"props":270,"children":271},{"__ignoreMap":7},[272,307],{"type":33,"tag":273,"props":274,"children":277},"span",{"class":275,"line":276},"line",1,[278,284,290,296,302],{"type":33,"tag":273,"props":279,"children":281},{"style":280},"--shiki-default:#F8F8F2",[282],{"type":38,"value":283},"notes.listRule   ",{"type":33,"tag":273,"props":285,"children":287},{"style":286},"--shiki-default:#FF79C6",[288],{"type":38,"value":289},"=",{"type":33,"tag":273,"props":291,"children":293},{"style":292},"--shiki-default:#E9F284",[294],{"type":38,"value":295}," '",{"type":33,"tag":273,"props":297,"children":299},{"style":298},"--shiki-default:#F1FA8C",[300],{"type":38,"value":301},"owner = @request.auth.id",{"type":33,"tag":273,"props":303,"children":304},{"style":292},[305],{"type":38,"value":306},"'\n",{"type":33,"tag":273,"props":308,"children":310},{"class":275,"line":309},2,[311,316,320,324,329],{"type":33,"tag":273,"props":312,"children":313},{"style":280},[314],{"type":38,"value":315},"notes.createRule ",{"type":33,"tag":273,"props":317,"children":318},{"style":286},[319],{"type":38,"value":289},{"type":33,"tag":273,"props":321,"children":322},{"style":292},[323],{"type":38,"value":295},{"type":33,"tag":273,"props":325,"children":326},{"style":298},[327],{"type":38,"value":328},"@request.auth.id != \"\" && owner = @request.auth.id",{"type":33,"tag":273,"props":330,"children":331},{"style":292},[332],{"type":38,"value":306},{"type":33,"tag":34,"props":334,"children":335},{},[336],{"type":38,"value":337},"Oder Inhalte sind öffentlich lesbar, geändert werden dürfen sie aber nur von angemeldeten Nutzern, die den Datensatz selbst angelegt haben:",{"type":33,"tag":263,"props":339,"children":341},{"className":265,"code":340,"language":267,"meta":7,"style":7},"articles.listRule   = ''\narticles.updateRule = '@request.auth.id != \"\" && author = @request.auth.id'\n",[342],{"type":33,"tag":150,"props":343,"children":344},{"__ignoreMap":7},[345,362],{"type":33,"tag":273,"props":346,"children":347},{"class":275,"line":276},[348,353,357],{"type":33,"tag":273,"props":349,"children":350},{"style":280},[351],{"type":38,"value":352},"articles.listRule   ",{"type":33,"tag":273,"props":354,"children":355},{"style":286},[356],{"type":38,"value":289},{"type":33,"tag":273,"props":358,"children":359},{"style":292},[360],{"type":38,"value":361}," ''\n",{"type":33,"tag":273,"props":363,"children":364},{"class":275,"line":309},[365,370,374,378,383],{"type":33,"tag":273,"props":366,"children":367},{"style":280},[368],{"type":38,"value":369},"articles.updateRule ",{"type":33,"tag":273,"props":371,"children":372},{"style":286},[373],{"type":38,"value":289},{"type":33,"tag":273,"props":375,"children":376},{"style":292},[377],{"type":38,"value":295},{"type":33,"tag":273,"props":379,"children":380},{"style":298},[381],{"type":38,"value":382},"@request.auth.id != \"\" && author = @request.auth.id",{"type":33,"tag":273,"props":384,"children":385},{"style":292},[386],{"type":38,"value":306},{"type":33,"tag":34,"props":388,"children":389},{},[390,392,398],{"type":38,"value":391},"Eine leere Regel bedeutet „für alle“, ",{"type":33,"tag":150,"props":393,"children":395},{"className":394},[],[396],{"type":38,"value":397},"null",{"type":38,"value":399}," bedeutet „nur Superuser“.",{"type":33,"tag":34,"props":401,"children":402},{},[403],{"type":38,"value":404},"Es gibt keine Middleware und keinen Endpoint, der das prüft. PocketBase wertet die Regeln bei jeder Anfrage selbst aus, auch wenn jemand die API direkt anspricht. Das spart eigene Berechtigungslogik.",{"type":33,"tag":34,"props":406,"children":407},{},[408],{"type":38,"value":409},"Wenn die Regeln nicht reichen, gibt es mehrere Wege:",{"type":33,"tag":74,"props":411,"children":412},{},[413,431,441],{"type":33,"tag":78,"props":414,"children":415},{},[416,421,423,429],{"type":33,"tag":84,"props":417,"children":418},{},[419],{"type":38,"value":420},"JS-Hooks und eigene Routen",{"type":38,"value":422}," (",{"type":33,"tag":150,"props":424,"children":426},{"className":425},[],[427],{"type":38,"value":428},"pb_hooks",{"type":38,"value":430},") direkt im Programm",{"type":33,"tag":78,"props":432,"children":433},{},[434,439],{"type":33,"tag":84,"props":435,"children":436},{},[437],{"type":38,"value":438},"Go-Erweiterung",{"type":38,"value":440},", wenn man PocketBase als Framework in ein eigenes Go-Projekt einbindet",{"type":33,"tag":78,"props":442,"children":443},{},[444,449,451,457],{"type":33,"tag":84,"props":445,"children":446},{},[447],{"type":38,"value":448},"Eigener Server davor.",{"type":38,"value":450}," Ein Nuxt- oder Node-Server kann das Login über ",{"type":33,"tag":150,"props":452,"children":454},{"className":453},[],[455],{"type":38,"value":456},"auth-refresh",{"type":38,"value":458}," prüfen und danach externe APIs aufrufen, deren Schlüssel nicht im Browser landen sollen.",{"type":33,"tag":34,"props":460,"children":461},{},[462,464,469],{"type":38,"value":463},"Die Grenze ist das Admin-Panel. ",{"type":33,"tag":84,"props":465,"children":466},{},[467],{"type":38,"value":468},"Es lässt sich nicht erweitern.",{"type":38,"value":470}," Es gibt keine eigenen Views, Dashboards oder Felder im Backend. Wer zum Beispiel eine eigene Nutzerverwaltung oder ein Dashboard braucht, muss es selbst in der App bauen, und für Kunden würde ich PocketBase nicht ohne eine eigene Oberfläche einsetzen. Bei Payload oder Statamic ist das einfacher.",{"type":33,"tag":174,"props":472,"children":474},{"id":473},"ki-kompatibilität",[475],{"type":38,"value":476},"KI-Kompatibilität",{"type":33,"tag":34,"props":478,"children":479},{},[480],{"type":38,"value":481},"Hier gibt es Licht und Schatten. Positiv ist, dass sich fast alles als Text beschreiben lässt:",{"type":33,"tag":74,"props":483,"children":484},{},[485,495,505,575],{"type":33,"tag":78,"props":486,"children":487},{},[488,493],{"type":33,"tag":84,"props":489,"children":490},{},[491],{"type":38,"value":492},"Migrationen sind JavaScript-Dateien im Repository.",{"type":38,"value":494}," Collections, Felder, Indizes und Regeln sind damit als Git-Diff nachvollziehbar, und ein Assistent kann neue Migrationen schreiben.",{"type":33,"tag":78,"props":496,"children":497},{},[498,503],{"type":33,"tag":84,"props":499,"children":500},{},[501],{"type":38,"value":502},"Regeln sind kurze Ausdrücke.",{"type":38,"value":504}," Man kann sie lesen, prüfen und per Prompt ändern.",{"type":33,"tag":78,"props":506,"children":507},{},[508,513,515,521,523,529,530,536,537,543,545,551,553,559,560,566,567,573],{"type":33,"tag":84,"props":509,"children":510},{},[511],{"type":38,"value":512},"Die API ist klein.",{"type":38,"value":514}," Das SDK besteht im Wesentlichen aus ",{"type":33,"tag":150,"props":516,"children":518},{"className":517},[],[519],{"type":38,"value":520},"getFullList",{"type":38,"value":522},", ",{"type":33,"tag":150,"props":524,"children":526},{"className":525},[],[527],{"type":38,"value":528},"getList",{"type":38,"value":522},{"type":33,"tag":150,"props":531,"children":533},{"className":532},[],[534],{"type":38,"value":535},"create",{"type":38,"value":522},{"type":33,"tag":150,"props":538,"children":540},{"className":539},[],[541],{"type":38,"value":542},"update",{"type":38,"value":544}," und ",{"type":33,"tag":150,"props":546,"children":548},{"className":547},[],[549],{"type":38,"value":550},"delete",{"type":38,"value":552},", dazu ",{"type":33,"tag":150,"props":554,"children":556},{"className":555},[],[557],{"type":38,"value":558},"filter",{"type":38,"value":522},{"type":33,"tag":150,"props":561,"children":563},{"className":562},[],[564],{"type":38,"value":565},"sort",{"type":38,"value":544},{"type":33,"tag":150,"props":568,"children":570},{"className":569},[],[571],{"type":38,"value":572},"expand",{"type":38,"value":574},".",{"type":33,"tag":78,"props":576,"children":577},{},[578,580,586],{"type":38,"value":579},"Für die Hooks bringt PocketBase eine Typdatei (",{"type":33,"tag":150,"props":581,"children":583},{"className":582},[],[584],{"type":38,"value":585},"types.d.ts",{"type":38,"value":587},") mit, die der Editor und der Assistent nutzen können.",{"type":33,"tag":34,"props":589,"children":590},{},[591,593,599,600,606,608,614],{"type":38,"value":592},"Weniger gut: Die Wahrheit über das Schema steckt in der Datenbank, nicht im Code. Im Gegensatz zu einem Code-First-System wie Payload sieht ein Assistent das Schema nur, wenn die Migrationen vollständig sind. Und die Wahrscheinlichkeit, dass er veralteten Code erzeugt, ist real. PocketBase hat sich mit Version 0.23 stark verändert, und viele Beispiele im Netz gehören noch zur alten API. Bei mir waren die ersten Migrationen gegen eine ältere Version geschrieben. Die Folge war, dass ",{"type":33,"tag":150,"props":594,"children":596},{"className":595},[],[597],{"type":38,"value":598},"created",{"type":38,"value":544},{"type":33,"tag":150,"props":601,"children":603},{"className":602},[],[604],{"type":38,"value":605},"updated",{"type":38,"value":607}," fehlten und eine Sortierung nach ",{"type":33,"tag":150,"props":609,"children":611},{"className":610},[],[612],{"type":38,"value":613},"-updated",{"type":38,"value":615}," mit einem 400-Fehler abbrach. Eine zusätzliche Migration hat das repariert.",{"type":33,"tag":34,"props":617,"children":618},{},[619,621,627],{"type":38,"value":620},"Mein Fazit: gut geeignet, solange man die Migrationen im Repository pflegt und dem Assistenten die verwendete Version mitgibt. Bei mir steht sie, zusammen mit den Regeln und Besonderheiten, in der ",{"type":33,"tag":150,"props":622,"children":624},{"className":623},[],[625],{"type":38,"value":626},"AGENTS.md",{"type":38,"value":574},{"type":33,"tag":174,"props":629,"children":631},{"id":630},"deployment",[632],{"type":38,"value":633},"Deployment",{"type":33,"tag":34,"props":635,"children":636},{},[637],{"type":38,"value":638},"Das ist für mich der größte Vorteil. Ein Deployment besteht aus einer Datei, und bei mir läuft es so:",{"type":33,"tag":640,"props":641,"children":642},"ol",{},[643,648,661,666],{"type":33,"tag":78,"props":644,"children":645},{},[646],{"type":38,"value":647},"GitHub Actions baut die Nuxt-App und lädt sie auf den Server.",{"type":33,"tag":78,"props":649,"children":650},{},[651,653,659],{"type":38,"value":652},"Die Dateien aus ",{"type":33,"tag":150,"props":654,"children":656},{"className":655},[],[657],{"type":38,"value":658},"pb_migrations/",{"type":38,"value":660}," werden hochgeladen.",{"type":33,"tag":78,"props":662,"children":663},{},[664],{"type":38,"value":665},"PocketBase wird per pm2 neu gestartet und wendet beim Start alle neuen Migrationen an.",{"type":33,"tag":78,"props":667,"children":668},{},[669,671,677],{"type":38,"value":670},"Ein Health-Check (",{"type":33,"tag":150,"props":672,"children":674},{"className":673},[],[675],{"type":38,"value":676},"/api/health",{"type":38,"value":678},") bestätigt, dass es wieder läuft.",{"type":33,"tag":34,"props":680,"children":681},{},[682,684,690,692,698,700,706],{"type":38,"value":683},"Es gibt keine Datenbank, die separat installiert und gewartet werden muss, und keine Abhängigkeiten, die gebaut werden müssen. Updates von PocketBase selbst sind ebenfalls eine neue Datei (",{"type":33,"tag":150,"props":685,"children":687},{"className":686},[],[688],{"type":38,"value":689},"./pocketbase update",{"type":38,"value":691},"). Auch der Betrieb ist einfach: PocketBase lauscht auf ",{"type":33,"tag":150,"props":693,"children":695},{"className":694},[],[696],{"type":38,"value":697},"127.0.0.1:8090",{"type":38,"value":699},", nginx leitet ",{"type":33,"tag":150,"props":701,"children":703},{"className":702},[],[704],{"type":38,"value":705},"/pb",{"type":38,"value":707}," dorthin und übernimmt SSL.",{"type":33,"tag":34,"props":709,"children":710},{},[711],{"type":38,"value":712},"Zwei Dinge sollte man beachten:",{"type":33,"tag":74,"props":714,"children":715},{},[716,726],{"type":33,"tag":78,"props":717,"children":718},{},[719,724],{"type":33,"tag":84,"props":720,"children":721},{},[722],{"type":38,"value":723},"Beim Neustart ist PocketBase kurz nicht erreichbar.",{"type":38,"value":725}," Für eine App dieser Größe ist das unkritisch.",{"type":33,"tag":78,"props":727,"children":728},{},[729,734,736,742],{"type":33,"tag":84,"props":730,"children":731},{},[732],{"type":38,"value":733},"Änderungen im Admin-Panel auf dem Server",{"type":38,"value":735}," erzeugen automatisch Migrationsdateien, aber eben dort und nicht im Repository. Wer Collections lieber im Panel klickt, muss die generierten Dateien anschließend vom Server herunterladen und ins Projekt übernehmen. Ich schreibe Migrationen deshalb bevorzugt von Hand. Auch Einstellungen wie die Gültigkeit des Login-Tokens (30 statt 7 Tage) stehen in einer Migration. Eine Änderung nur im Panel würde sonst bei einem frischen ",{"type":33,"tag":150,"props":737,"children":739},{"className":738},[],[740],{"type":38,"value":741},"pb_data",{"type":38,"value":743}," verloren gehen.",{"type":33,"tag":174,"props":745,"children":747},{"id":746},"wenig-ressourcen-und-kostenlos",[748],{"type":38,"value":749},"Wenig Ressourcen und kostenlos",{"type":33,"tag":34,"props":751,"children":752},{},[753],{"type":38,"value":754},"Die Datei ist rund 32 MB groß. Auf meinem Rechner belegte der Prozess mit rund 500 Datensätzen etwa 60 MB Arbeitsspeicher. Es gibt keine Lizenz, keine Preise pro Nutzer und keine Beschränkung auf ein Projekt. Dafür muss man sich um Server und Backups selbst kümmern.",{"type":33,"tag":62,"props":756,"children":758},{"id":757},"nachteile",[759],{"type":38,"value":760},"Nachteile",{"type":33,"tag":174,"props":762,"children":764},{"id":763},"serveranforderungen",[765],{"type":38,"value":766},"Serveranforderungen",{"type":33,"tag":34,"props":768,"children":769},{},[770,772,777,779,784],{"type":38,"value":771},"Hier muss man unterscheiden. Die Anforderungen sind ",{"type":33,"tag":84,"props":773,"children":774},{},[775],{"type":38,"value":776},"gering",{"type":38,"value":778},", aber PocketBase ist ",{"type":33,"tag":84,"props":780,"children":781},{},[782],{"type":38,"value":783},"kein statisches Hosting",{"type":38,"value":785},". Es braucht:",{"type":33,"tag":74,"props":787,"children":788},{},[789,794,811,816],{"type":33,"tag":78,"props":790,"children":791},{},[792],{"type":38,"value":793},"einen dauerhaft laufenden Prozess, bei mir mit pm2",{"type":33,"tag":78,"props":795,"children":796},{},[797,802,804,809],{"type":33,"tag":84,"props":798,"children":799},{},[800],{"type":38,"value":801},"persistenten Speicher",{"type":38,"value":803}," für den Ordner ",{"type":33,"tag":150,"props":805,"children":807},{"className":806},[],[808],{"type":38,"value":741},{"type":38,"value":810}," mit Datenbank und Uploads",{"type":33,"tag":78,"props":812,"children":813},{},[814],{"type":38,"value":815},"einen Reverse Proxy für HTTPS",{"type":33,"tag":78,"props":817,"children":818},{},[819],{"type":38,"value":820},"Shared Hosting ist damit raus. Ein kleiner VPS reicht aber aus, und PocketBase teilt sich bei mir den Server mit der Nuxt-App.",{"type":33,"tag":34,"props":822,"children":823},{},[824,826,831],{"type":38,"value":825},"Verglichen mit einem Node-basierten CMS ist das ein Vorteil, verglichen mit einem Baukasten oder statischen Seiten ein Nachteil. Dazu kommt, dass ",{"type":33,"tag":84,"props":827,"children":828},{},[829],{"type":38,"value":830},"SQLite genau einen Schreibzugriff gleichzeitig",{"type":38,"value":832}," zulässt und sich nicht auf mehrere Server verteilen lässt. Für eine App mit einem Server ist das kein Problem. Wer horizontal skalieren will, hat mit PocketBase aber ein Problem.",{"type":33,"tag":174,"props":834,"children":836},{"id":835},"kein-echtes-sql",[837],{"type":38,"value":838},"Kein echtes SQL",{"type":33,"tag":34,"props":840,"children":841},{},[842,844,849],{"type":38,"value":843},"Das stimmt nur zum Teil, deshalb eine genauere Einordnung. Unter der Haube ist PocketBase ",{"type":33,"tag":84,"props":845,"children":846},{},[847],{"type":38,"value":848},"echtes SQL",{"type":38,"value":850},", denn die Daten liegen in einer SQLite-Datei. Der Zugriff von außen läuft aber nicht über SQL, sondern über die eigene API mit einer eigenen Filter-Syntax:",{"type":33,"tag":263,"props":852,"children":854},{"className":265,"code":853,"language":267,"meta":7,"style":7},"const defaults = await pb.collection('cards').getFullList({\n  filter: 'owner = \"\"',\n})\n\nconst progress = await pb.collection('card_progress').getFullList({\n  expand: 'card',\n})\n",[855],{"type":33,"tag":150,"props":856,"children":857},{"__ignoreMap":7},[858,923,954,963,973,1031,1061],{"type":33,"tag":273,"props":859,"children":860},{"class":275,"line":276},[861,866,871,875,880,885,891,896,901,905,909,914,918],{"type":33,"tag":273,"props":862,"children":863},{"style":286},[864],{"type":38,"value":865},"const",{"type":33,"tag":273,"props":867,"children":868},{"style":280},[869],{"type":38,"value":870}," defaults ",{"type":33,"tag":273,"props":872,"children":873},{"style":286},[874],{"type":38,"value":289},{"type":33,"tag":273,"props":876,"children":877},{"style":286},[878],{"type":38,"value":879}," await",{"type":33,"tag":273,"props":881,"children":882},{"style":280},[883],{"type":38,"value":884}," pb.",{"type":33,"tag":273,"props":886,"children":888},{"style":887},"--shiki-default:#50FA7B",[889],{"type":38,"value":890},"collection",{"type":33,"tag":273,"props":892,"children":893},{"style":280},[894],{"type":38,"value":895},"(",{"type":33,"tag":273,"props":897,"children":898},{"style":292},[899],{"type":38,"value":900},"'",{"type":33,"tag":273,"props":902,"children":903},{"style":298},[904],{"type":38,"value":190},{"type":33,"tag":273,"props":906,"children":907},{"style":292},[908],{"type":38,"value":900},{"type":33,"tag":273,"props":910,"children":911},{"style":280},[912],{"type":38,"value":913},").",{"type":33,"tag":273,"props":915,"children":916},{"style":887},[917],{"type":38,"value":520},{"type":33,"tag":273,"props":919,"children":920},{"style":280},[921],{"type":38,"value":922},"({\n",{"type":33,"tag":273,"props":924,"children":925},{"class":275,"line":309},[926,931,936,940,945,949],{"type":33,"tag":273,"props":927,"children":928},{"style":280},[929],{"type":38,"value":930},"  filter",{"type":33,"tag":273,"props":932,"children":933},{"style":286},[934],{"type":38,"value":935},":",{"type":33,"tag":273,"props":937,"children":938},{"style":292},[939],{"type":38,"value":295},{"type":33,"tag":273,"props":941,"children":942},{"style":298},[943],{"type":38,"value":944},"owner = \"\"",{"type":33,"tag":273,"props":946,"children":947},{"style":292},[948],{"type":38,"value":900},{"type":33,"tag":273,"props":950,"children":951},{"style":280},[952],{"type":38,"value":953},",\n",{"type":33,"tag":273,"props":955,"children":957},{"class":275,"line":956},3,[958],{"type":33,"tag":273,"props":959,"children":960},{"style":280},[961],{"type":38,"value":962},"})\n",{"type":33,"tag":273,"props":964,"children":966},{"class":275,"line":965},4,[967],{"type":33,"tag":273,"props":968,"children":970},{"emptyLinePlaceholder":969},true,[971],{"type":38,"value":972},"\n",{"type":33,"tag":273,"props":974,"children":976},{"class":275,"line":975},5,[977,981,986,990,994,998,1002,1006,1010,1015,1019,1023,1027],{"type":33,"tag":273,"props":978,"children":979},{"style":286},[980],{"type":38,"value":865},{"type":33,"tag":273,"props":982,"children":983},{"style":280},[984],{"type":38,"value":985}," progress ",{"type":33,"tag":273,"props":987,"children":988},{"style":286},[989],{"type":38,"value":289},{"type":33,"tag":273,"props":991,"children":992},{"style":286},[993],{"type":38,"value":879},{"type":33,"tag":273,"props":995,"children":996},{"style":280},[997],{"type":38,"value":884},{"type":33,"tag":273,"props":999,"children":1000},{"style":887},[1001],{"type":38,"value":890},{"type":33,"tag":273,"props":1003,"children":1004},{"style":280},[1005],{"type":38,"value":895},{"type":33,"tag":273,"props":1007,"children":1008},{"style":292},[1009],{"type":38,"value":900},{"type":33,"tag":273,"props":1011,"children":1012},{"style":298},[1013],{"type":38,"value":1014},"card_progress",{"type":33,"tag":273,"props":1016,"children":1017},{"style":292},[1018],{"type":38,"value":900},{"type":33,"tag":273,"props":1020,"children":1021},{"style":280},[1022],{"type":38,"value":913},{"type":33,"tag":273,"props":1024,"children":1025},{"style":887},[1026],{"type":38,"value":520},{"type":33,"tag":273,"props":1028,"children":1029},{"style":280},[1030],{"type":38,"value":922},{"type":33,"tag":273,"props":1032,"children":1034},{"class":275,"line":1033},6,[1035,1040,1044,1048,1053,1057],{"type":33,"tag":273,"props":1036,"children":1037},{"style":280},[1038],{"type":38,"value":1039},"  expand",{"type":33,"tag":273,"props":1041,"children":1042},{"style":286},[1043],{"type":38,"value":935},{"type":33,"tag":273,"props":1045,"children":1046},{"style":292},[1047],{"type":38,"value":295},{"type":33,"tag":273,"props":1049,"children":1050},{"style":298},[1051],{"type":38,"value":1052},"card",{"type":33,"tag":273,"props":1054,"children":1055},{"style":292},[1056],{"type":38,"value":900},{"type":33,"tag":273,"props":1058,"children":1059},{"style":280},[1060],{"type":38,"value":953},{"type":33,"tag":273,"props":1062,"children":1064},{"class":275,"line":1063},7,[1065],{"type":33,"tag":273,"props":1066,"children":1067},{"style":280},[1068],{"type":38,"value":962},{"type":33,"tag":34,"props":1070,"children":1071},{},[1072],{"type":38,"value":1073},"Das ist für die meisten Abfragen angenehm, aber nicht mehr als das, was die API anbietet:",{"type":33,"tag":74,"props":1075,"children":1076},{},[1077,1093,1105],{"type":33,"tag":78,"props":1078,"children":1079},{},[1080,1085,1087,1092],{"type":33,"tag":84,"props":1081,"children":1082},{},[1083],{"type":38,"value":1084},"Joins",{"type":38,"value":1086}," gibt es nur über Relationen und ",{"type":33,"tag":150,"props":1088,"children":1090},{"className":1089},[],[1091],{"type":38,"value":572},{"type":38,"value":574},{"type":33,"tag":78,"props":1094,"children":1095},{},[1096,1098,1103],{"type":38,"value":1097},"Auswertungen mit ",{"type":33,"tag":84,"props":1099,"children":1100},{},[1101],{"type":38,"value":1102},"GROUP BY, Summen oder Zählungen",{"type":38,"value":1104}," sind über die API nicht möglich.",{"type":33,"tag":78,"props":1106,"children":1107},{},[1108],{"type":38,"value":1109},"Wer ein anderes Tool anbinden will, kann sich nicht einfach mit einer Datenbank verbinden. Die Datei gehört PocketBase, und ein zweiter Zugriff von außen ist keine gute Idee.",{"type":33,"tag":34,"props":1111,"children":1112},{},[1113,1115,1120],{"type":38,"value":1114},"Es gibt Auswege. ",{"type":33,"tag":84,"props":1116,"children":1117},{},[1118],{"type":38,"value":1119},"View Collections",{"type":38,"value":1121}," definiert man selbst mit einem SQL-SELECT, sie sind schreibgeschützt, lassen sich aber wie jede andere Collection abfragen. In den Hooks kann man außerdem rohes SQL ausführen. Wer von Anfang an mit Joins, Berichten und komplexen Abfragen rechnet, sollte stattdessen eine klassische Datenbank wie Postgres wählen. PocketBase unterstützt keine andere Datenbank als SQLite.",{"type":33,"tag":174,"props":1123,"children":1125},{"id":1124},"der-stand-vor-version-10",[1126],{"type":38,"value":1127},"Der Stand vor Version 1.0",{"type":33,"tag":34,"props":1129,"children":1130},{},[1131],{"type":38,"value":1132},"PocketBase ist weiterhin Version 0.x. Das Projekt weist selbst darauf hin, dass die Abwärtskompatibilität vor 1.0 nicht garantiert ist. Der Umstieg auf 0.23 hat das gezeigt: Collections und Migrationen wurden umgebaut, und alte Beispiele funktionieren nicht mehr. Mit eigenen Migrationen und festen Versionen ist das beherrschbar, ein Update sollte man trotzdem bewusst einplanen und nicht beim nächsten Deploy nebenbei mitnehmen.",{"type":33,"tag":34,"props":1134,"children":1135},{},[1136],{"type":38,"value":1137},"Dazu kommt, dass PocketBase im Wesentlichen von einer einzelnen Person betreut wird. Das Projekt ist sehr aktiv und stabil, trotzdem muss man bei einem langlebigen Produkt mit diesem Risiko leben. Weil die Daten in einer SQLite-Datei liegen und die API-Regeln lesbar sind, wäre ein späterer Umzug aber möglich.",{"type":33,"tag":174,"props":1139,"children":1141},{"id":1140},"daten-und-backups-liegen-nicht-im-repository",[1142],{"type":38,"value":1143},"Daten und Backups liegen nicht im Repository",{"type":33,"tag":34,"props":1145,"children":1146},{},[1147,1149,1154],{"type":38,"value":1148},"Wie bei jedem Backend mit Datenbank gehören die Inhalte nicht zu Git. Die Migrationen beschreiben nur die Struktur. Startet PocketBase ohne den Ordner ",{"type":33,"tag":150,"props":1150,"children":1152},{"className":1151},[],[1153],{"type":38,"value":741},{"type":38,"value":1155},", in dem die Datenbank liegt (auf einem neuen Rechner oder einem neu aufgesetzten Server), beginnt es mit einer leeren Datenbank. Die Migrationen legen dann die Struktur an, aber keine Inhalte, und die App startet mit leeren Collections.",{"type":33,"tag":34,"props":1157,"children":1158},{},[1159],{"type":38,"value":1160},"PocketBase kann zwar automatisch Backups erstellen, auch in einen S3-Speicher, aber das muss man einrichten und gelegentlich testen. Ein Backup ist erst dann eins, wenn man es zurückspielen kann.",{"type":33,"tag":174,"props":1162,"children":1164},{"id":1163},"kleine-stolpersteine",[1165],{"type":38,"value":1166},"Kleine Stolpersteine",{"type":33,"tag":74,"props":1168,"children":1169},{},[1170,1196,1214],{"type":33,"tag":78,"props":1171,"children":1172},{},[1173,1178,1180,1186,1188,1194],{"type":33,"tag":84,"props":1174,"children":1175},{},[1176],{"type":38,"value":1177},"Regeln vergleichen Text exakt.",{"type":38,"value":1179}," Ein Vergleich mit ",{"type":33,"tag":150,"props":1181,"children":1183},{"className":1182},[],[1184],{"type":38,"value":1185},"ana@x.com",{"type":38,"value":1187}," trifft nicht auf ",{"type":33,"tag":150,"props":1189,"children":1191},{"className":1190},[],[1192],{"type":38,"value":1193},"Ana@X.com",{"type":38,"value":1195}," zu. E-Mail-Adressen sollte man deshalb schon im Formular kleinschreiben.",{"type":33,"tag":78,"props":1197,"children":1198},{},[1199,1204,1206,1212],{"type":33,"tag":84,"props":1200,"children":1201},{},[1202],{"type":38,"value":1203},"E-Mail-Adressen sind standardmäßig unsichtbar.",{"type":38,"value":1205}," PocketBase gibt sie nur dem Besitzer des Datensatzes zurück. Für die Nutzerliste im Admin-Bereich musste ich ",{"type":33,"tag":150,"props":1207,"children":1209},{"className":1208},[],[1210],{"type":38,"value":1211},"emailVisibility",{"type":38,"value":1213}," setzen.",{"type":33,"tag":78,"props":1215,"children":1216},{},[1217,1222],{"type":33,"tag":84,"props":1218,"children":1219},{},[1220],{"type":38,"value":1221},"Ohne SMTP keine Mails.",{"type":38,"value":1223}," Solange kein Mailserver konfiguriert ist, kann PocketBase weder Bestätigungs-Mails noch Passwort-Resets verschicken.",{"type":33,"tag":62,"props":1225,"children":1227},{"id":1226},"fazit",[1228],{"type":38,"value":1229},"Fazit",{"type":33,"tag":34,"props":1231,"children":1232},{},[1233],{"type":38,"value":1234},"PocketBase passt gut zu kleinen bis mittleren Apps mit Login, Nutzerdaten und klaren Zugriffsregeln, vor allem wenn man das Backend selbst betreibt und möglichst wenig Infrastruktur will. Das Admin-Panel ist modern, die Regeln sparen viel Backend-Code, und das Deployment ist so einfach, wie es bei einem Backend mit Datenbank sein kann. Mit Migrationen im Repository arbeiten auch KI-Assistenten gut damit.",{"type":33,"tag":34,"props":1236,"children":1237},{},[1238,1240,1246,1248,1254],{"type":38,"value":1239},"Die Nachteile sind real: Es gibt kein SQL über die API, das Admin-Panel lässt sich nicht erweitern, die Skalierung ist auf einen Server begrenzt, und mit Version 0.x muss man Updates sorgfältig planen. Für Projekte, in denen Redakteure Inhalte pflegen, würde ich ein CMS wie ",{"type":33,"tag":41,"props":1241,"children":1243},{"href":1242},"/blog/payload",[1244],{"type":38,"value":1245},"Payload",{"type":38,"value":1247}," oder ",{"type":33,"tag":41,"props":1249,"children":1251},{"href":1250},"/blog/statamic",[1252],{"type":38,"value":1253},"Statamic",{"type":38,"value":1255}," wählen. PocketBase ist ein interessantes Produkt, aber für ein neues Projekt würde ich mich für ein anderes System wie Payload oder Appwrite entscheiden.",{"type":33,"tag":1257,"props":1258,"children":1259},"style",{},[1260],{"type":38,"value":1261},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":309,"depth":309,"links":1263},[1264,1265,1272,1279],{"id":64,"depth":309,"text":67},{"id":169,"depth":309,"text":172,"children":1266},[1267,1268,1269,1270,1271],{"id":176,"depth":956,"text":179},{"id":253,"depth":956,"text":256},{"id":473,"depth":956,"text":476},{"id":630,"depth":956,"text":633},{"id":746,"depth":956,"text":749},{"id":757,"depth":309,"text":760,"children":1273},[1274,1275,1276,1277,1278],{"id":763,"depth":956,"text":766},{"id":835,"depth":956,"text":838},{"id":1124,"depth":956,"text":1127},{"id":1140,"depth":956,"text":1143},{"id":1163,"depth":956,"text":1166},{"id":1226,"depth":309,"text":1229},"markdown","content:blog:pocketbase.md","content","blog/pocketbase.md","md",1791545016482]