IceBase keeps Firebase's API and makes the backend a config value: firebase, emulator on SQLite, or vm on Postgres, per service. It is Apache-2.0, on JSR, and at v0.6.0: github.com/Panth977/icebase. The full overnight move off Firebase is not finished, and I'll say below what is missing.
I'm Panth Patel. I build a lot of side projects, and Firebase has been my go-to for them. It has everything, and the free tier is generous.
The problem shows up late
The trouble starts when a side project stops being one. Once I wanted to run something for a company or at scale, I found no overnight way off. The app is written against Firebase's SDK, and the schema exists only in the data.
So I wanted a layer I could adopt on day one, while Firebase is still the right choice, that keeps the exit open.
Same calls, one more file
The function names and call shapes are Firebase's. The imports change:
import { collection, getDocs, orderBy, query, where } from '@panth977/icebase/firestore'; import { db } from './lib/icebase'; import { products } from '../icebase/schema'; const q = query(collection(db, products), where('category', '==', 'shoes'), orderBy('priceCents')); const snap = await getDocs(q); // snap.docs[0].data() is typed from the schema The one difference you'll feel is key paths. Nested fields are arrays, where(['address', 'city'], ...), because 'rating.avg' is one key.
icebase.config.ts is the new file. It holds the schema, rules, functions, hosting and, per environment, the mode of each service:
environments: { local: { mode: 'emulator' }, prod: { mode: 'firebase' }, } The schema is the real feature
Firebase accepts anything, and that is why it is fast to start. AI makes this sharper. Building with AI is fine, but a good developer still looks at the interfaces and the schema to find bugs before the build. A loose database gives you nothing to look at.
IceBase asks for the exact shape of every collection, Realtime Database path and storage path, and for every field you filter on. Writes are type-checked and validated at runtime in every mode. A query whose index isn't declared fails at compile time:
MissingIndex<"category" | "stock"> It is also refused at runtime, in every mode, and icebase indexes check lists every such query for CI.
The later payoff is the migration. On the day you move to Postgres, you don't need to run an AI over your data to discover what the schema was. It has been in your repo the whole time.
If the project already exists, icebase init --import DIR --project ID --sample N drafts a schema from sampled documents, and learning mode reports undeclared collections and missing indexes instead of refusing them.
Three drivers
- firebase: the real Firebase SDKs, with the type, schema and index checks on top.
- emulator: my own IceBase server on SQLite. I found Firebase's emulator slow and wanted one good enough for production-like testing. It enforces your rules in Firebase's own syntax, and those same files deploy unchanged to Firebase.
- vm: the same server on Postgres, self-hosted.
Modes mix per service, so one service can move while the rest stay put. Going between the two server stores is one command, in either direction:
icebase migrate --from sqlite:.icebase --to env:DATABASE_URL IceBase also has its own console for every mode, so you don't have to open Firebase's.
To keep me honest, the conformance suites run against Firebase's emulators, the IceBase server and a live Firebase project, and every IceBase-server suite also passes on Postgres. My design editor, Digital Canvas, runs on IceBase in firebase mode.
What is not built
- S3 for stored files. In vm mode they stay on disk today, and
icebase migratedoes not copy them. - A Docker image, login and deploy to a VM, and backups.
- Import, export and link. These are what will make the move from a live Firebase project an overnight job, and they are next on the roadmap.
So the overnight move is the goal, and the pieces under it are in place. It is not a claim about today.
Have you moved a real app off Firebase? What was the part you did not see coming?
