Coming from Firebase

This page is the practical mapping; the side-by-side is the decision.

What maps

Firebase vs Pithy is the honest comparison to read first, is Pithy for you? is the decision, the data layer is what replaces Firestore, and coming from a Node backend covers the runtime differences.

FirebaseHere
Authenticationauth
Cloud Storagestorage, on R2
Cloud FunctionsYour own routes, and Workflows for anything slow
Remote ConfigYour config, deployed
ExtensionsCapabilities

Two changes do most of the work

Firestore becomes SQL. Documents and subcollections become tables and foreign keys. A denormalized document that existed to make one read cheap usually becomes a join — which is what a relational database is good at, and what a document store made you avoid.

The client stops talking to the database. There is no SDK querying your data from a browser, so there are no security rules — because there is nothing to write rules against.

Security rules have no equivalent, and do not need one

Every read goes through your Worker, and authorization is a guard on the route:

app.get("/boards/:id", requireAuth(), async (c) => { … });

You lose a declarative policy language evaluated at the database.

You gain the reason it existed: no client holds a key that reaches your data, so a rule you forgot to write is not an open collection.

Realtime is not a change feed here

Firestore pushes document changes to listeners.

Here a WebSocket is a Durable Object you write — and what it pushes is what your server decided to push. Multiplayer’s session socket and matchmaking’s presence object are the two the kit ships.

If your app is built on snapshot listeners over collections, that is real work, not a config line. Say so before you commit.

The offline story is different

Firestore’s SDK caches and syncs offline. There is no equivalent here — your client owns its own offline behavior.

Money moves

If you are on RevenueCat alongside Firebase, see coming from RevenueCat — payments here is a different shape.

Cloud Functions become two things

A synchronous handler becomes a route.

A background function becomes a Workflow — durable, journalled steps, resuming where it died. That is the pattern for anything a request’s CPU budget cannot hold.

A scheduled function becomes a cron plus a Workflow, declared in your app capability and written by pithy worker sync.

Migrating the data

Export, transform, import. A document export is JSON; the transform is where you decide what becomes a table and what becomes a column.

Do the schema first. One Zod object per table is the whole definition here — the app shape, the row shape, and the codecs between them.

What you give up

A mature client SDK that does auth, data, storage and analytics in one dependency.

Analytics, Crashlytics, and the rest of the console. None of that is here.

Google’s operational scale behind your database.

What you get

SQL you can join and query, in a database you own.

No client key that reaches your data.

No per-document read billing to design around.

ESC