PocketBase Role¶
PocketBase isn’t “a backend library we call.” It plays the role of the system’s shared source of truth for Studio data, plus the identity + realtime coordination layer that keeps multiple parts of the UI in sync.
PocketBase is the place where “what the user has” lives (records + files), and it’s also the place that tells the UI “something changed” (subscriptions). The frontend mostly orchestrates; PocketBase authorizes, stores, and broadcasts.
Here’s what that looks like in this project, with concrete examples from the code.
PocketBase as the system’s “Studio state authority”¶
Most of the “Studio” domain objects are represented as PocketBase collections (or views) and are read/updated directly via pb.collection(...). You can see this in several places:
- Assets are fetched directly from PocketBase when we want batch lookup by ids:
// src/api-client/assets/assets.api.ts
return await pb.collection("assets").getFullList({ filter });
- Cards are created/updated in PocketBase and their order is managed by updating other records first:
// src/components/studio/services/cardService.ts
pb.collection("cards").update(card.id, { order: index + 1 })
This is PocketBase acting as the canonical datastore for Studio objects.
PocketBase as the realtime “event bus” for UI consistency¶
The UI doesn’t just fetch from PocketBase; it subscribes to changes so screens update automatically.
A good example is useFileUrls, which subscribes to the files collection and refreshes URLs when anything changes:
// src/hooks/useReactiveFileUrls.ts
const unsubscribePromise = pb.collection("files").subscribe("*", fetchAllUrls);
And for assets, there’s a pattern where changes trigger a refetch via React Query invalidation:
// src/hooks/useReactiveFileUrls.ts
pb.collection("files").subscribe("*", () => {
queryClient.invalidateQueries({ queryKey: queriesKey.assetFiles(assetIds) });
});
This is PocketBase doing the “coordination” job: when data changes, the UI gets nudged to reconcile.
PocketBase as the identity and session authority¶
Auth is PocketBase-first here. The project keeps authentication state in PocketBase’s authStore, and higher-level app state (Zustand) mirrors it.
Email/password login:
// src/components/studio/stores/useAuthStore.ts
await pb.collection("users").authWithPassword(username, password);
OAuth login is also driven through PocketBase’s user auth methods. The store fetches available OAuth providers from PocketBase and then completes the OAuth code exchange through PocketBase:
// src/components/studio/stores/useAuthStore.ts
const authMethods = await pb.collection("users").listAuthMethods();
await pb.collection("users").authWithOAuth2Code(provider, code, codeVerifier, redirectUrl, {...});
So in role terms: PocketBase is the “identity broker” the frontend trusts.
PocketBase as the file vault and media URL authority¶
Files aren’t treated as random blob uploads. The project models them as first-class records in a files collection, with metadata and relationships (project, card, generation, thumbnail, etc.).
Uploading a file is implemented as “create a PocketBase record with FormData,” which triggers PocketBase-managed storage:
// src/components/studio/services/fileService.ts
const record = await pb.collection("files").create(formData);
Generating a usable URL is also PocketBase-native (PocketBase decides the correct URL format, optional thumbs, etc.):
// src/components/studio/services/fileService.ts
return pb.files.getURL(fileRecord, filename, queryParams);
There’s also a smart lookup path for assets via a PocketBase view view_asset_files:
// src/components/studio/services/fileService.ts
await pb.collection("view_asset_files").getFullList({ filter: `asset_id='${assetId}'` });
That’s PocketBase acting as the “media index + delivery gateway.”
PocketBase as a configurable environment dependency¶
PocketBase’s base URL is not hardcoded. It’s read from env, then updated from a Next.js endpoint and cached in sessionStorage:
// src/components/studio/lib/pocketbase.ts
fetch("/api/secrets-config").then(...).then((data) => { pb.baseURL = data.pocketbase.url; })
And the endpoint exposes the PocketBase URL (and other service URLs) from server env:
So operationally: PocketBase is a deploy-time system dependency, discovered/configured at runtime.
How this coexists with the “other backend” APIs¶
You’ll notice the project also has a large typed API layer (services/*RequestApi) and a RequestApiService. That’s a separate role: more traditional request/response APIs for domains that aren’t purely “Studio realtime state.”
In practice, the split looks like:
- PocketBase: “live Studio data, auth, files, realtime subscriptions”
- RequestApiService: “domain services that may not be PocketBase-backed, or need more complex backend orchestration”
You can even see both in the same module, e.g. assets.api.ts uses the typed service for most operations, but uses PocketBase directly for “batch by ids” and realtime subscriptions.