Skip to main content

Convex vs. Firebase

Convex and Google's Cloud Firestore are both platforms for managing global state in modern serverless applications. Both platforms store documents and notify applications of document changes in realtime without requiring application developers to manage their own infrastructure.

At Convex we have a lot of respect for Firebase. We're building this product because we also believe that developers deserve a comprehensive platform for global state management. Firebase was one of the first teams to work on this problem and has done a lot of innovation in this space.

That being said, there are some important differences between the two platforms:

  • Backend API
  • React Integration
  • End-to-End Correctness Philosophy
note

"Firebase" actually refers to two separate products: Firebase Realtime Database and Cloud Firestore. This discussion focuses on Cloud Firestore because it's the newer, more scalable Firebase offering.

Backend API: Documents or Functions?

The biggest architectural difference between Convex and Cloud Firestore is how your application interacts with its data.

With Cloud Firestore, the client interacts with its data by loading documents straight from the database. If you're building a chat application and you store users and messages in Cloud Firestore, your application code will also interact with those same user and message documents.

In Convex, all access to documents is mediated through Convex functions. These functions can return documents straight from the database, but also can return data derived from these documents.

Having this additional layer on the server is important for 2 reasons:

  1. Avoiding serial request waterfalls.
  2. Encapsulating business logic.

Avoiding Serial Request Waterfalls

Serial request waterfalls happen when an application uses multiple, serial requests to load its data. This harms page load performance because rendering your site will now require multiple network round trips to the backend, one after another, to render the core content.

With Cloud Firestore, a chat app might do one query from the client to load the messages in the app and then a subsequent query for each message to load the user that wrote it:

// Client code in a Cloud Firestore chat app.
// This loads the messages and users using multiple round trips.
const querySnapshot = await getDocs(collection(db, "messages"));
const userSnapshots = await Promise.all(
querySnapshot.docs().map(async messageSnapshot => {
return await getDoc(docSnapshot.data().creator);
})
);

The key thing to notice here is this is happening client-side. This code is doing serial queries to load data over the user's slow network connection. In a complex application, you will end up with many round trips and a slow, multi-step loading experience.

Even if you model your Cloud Firestore data using subcollections (which require your data to be hierarchical) you still can't do a deep query to load a document and its subcollection documents.

In Convex you have full control over the data you send the client because it's just the result of JavaScript/TypeScript functions. It's easy to bundle the messages in a chat app along with the author that wrote each one. Our Users and Auth tutorial does exactly that!

// Client code in a Convex chat app.
// This loads the messages and users all in a single round trip.
const messages = useQuery("listMessages");

Here the messages and users are all being loaded on the server inside of the listMessages.js query function so there is only a single round trip from the client.

In Cloud Firestore the only option to avoid request waterfalls is to load your data in a Cloud Function but that involves sacrificing reactivity and optimistic updates.

Encapsulating Business Logic

Another important feature of Convex functions is that they can serve as a business logic layer. While sending raw documents to the client can be a great way to get started on an app, as your app grows in complexity you'll want to put logic on the server. Here are a few common examples of where server-side logic is necessary:

  • Your mobile, web apps, and public API all need to share common logic like rendering notifications into a human-readable string.
  • Some mutations require custom validation like checking that an email address is formatted correctly.
  • You want to change your database schema to support more complex queries but want to present the same API to your apps to avoid migrating application code as well.

Convex functions serve as a natural place to put all of this logic. As a developer, you can share logic between all of your platforms and ensure that even malicious users only interact with your data through this layer.

Once again, with Cloud Firestore you could put this code into Cloud Functions and use them as a business logic layer, but it's messy and requires giving up some of the platform's other features like reactivity and optimistic updates.

React Integration

Convex was designed to be used with React.

All of our tutorials are React apps. Our React Hooks make it dead simple to load and edit data within React components.

Cloud Firestore was not designed with React in mind.

Cloud Firestore has a JavaScript SDK, but it leaves getting your data into your React components up to the developer. In particular, Cloud Firestore supports listening for realtime updates with onSnapshot but correctly binding those snapshots to React components is complex enough that many developers give up on end-to-end reactivity.

End-to-End Correctness Philosophy

Convex has an almost fanatical obsession with correctness.

We don't just want it to be possible to build a correct app but we want it to be impossible not to build your app correctly. Developers should fall into the pit of success.

Correctness doesn't stop at the backend API. At Convex we believe correctness should extend all the way to the users of the application. Users should always see consistent, up-to-date state. When users modify state, their modifications should always occur transactionally and exactly once.

Here are a few places where our philosophy around correctness differs from Cloud Firestore's:

Reactivity

In the ConvexReactClient, all queries are fully reactive and receive realtime updates.

In Cloud Firestore, you can receive realtime updates but it's extra work to manage the onUpdate callbacks (especially in React) so developers normally choose whether they want to load reactive or non-reactive data on a case-by-case basis. This leads to applications that show users a mix of fresh and stale data.

Transactions

In Convex, every mutation is a transaction running on a consistent database snapshot.

In Cloud Firestore, you can run transactions but they have complex requirements. All of the reads in a transaction must precede all of the writes and you must always remember not to edit application state in the transaction. Often developers instead opt to use the simpler APIs to update documents and are surprised down the road when one logical user action was only partially saved to the database.

Bottom Line

Convex is designed to both make it easy to get started on the platform and support you as your app grows.

Our React integration makes it a breeze to start interacting with your global shared state and loading fully reactive data. As your app grows, you'll appreciate the ability to push logic into query and mutation functions and keep your app fast by avoiding serial request waterfalls.

On Cloud Firestore, you'll have to do some acrobatics to integrate with React and listen for realtime updates. Down the road, you'll find your app full of slow loading experiences from request waterfalls and your database full of half-committed mutations.

Convex is designed to help you evolve your application as it grows, not to push you onto another platform. Soon we'll be adding support for enforcing database schemas and running database migrations so that you'll be able to seamlessly grow your app on Convex.

What are you waiting for? Get started with Convex!