Migrating from the official Firestore SDK to Firebase-Firestore-Lite

The official Firestore SDK for JavaScript is pretty big - and if you want to use auth too then you're easily looking at 130KB of gzip compressed code. The size of the bundles is a known issue, and there is even a firebase alpha SDK on the horizon that is set to cut the size by up to 80%.

But if you don't want to wait for the alpha to turn into a production release there is an unofficial alternative in the firebase-firestore-lite package. I've migrated apps and seen 90% size reduction with no loss in functionality (although I'm using basic CRUD only - no realtime or offline support). If you'd like more detail about the performance benefits of migrating see the benchmarks.

The rest of this post will cover what migration looks for Firestore with Google auth.

This is not a drop-in replacement - the alternative SDK has a different (simpler!) API and doesn't have feature parity with the official SDK. For details on what features are missing see what am i giving up by using this.

Updating Packages

In order to use Firestore with Google auth we need to use both the firebase-auth-lite and firebase-firestore-lite packages. We can also remove the firebase package:

yarn remove firebase

yarn add firebase-auth-lite firebase-firestore-lite

Configuring Auth

Rather than needing an instance of firebase (probably instantiated with firebase.initializeApp({...}) you pass your API key directly to auth. You can delete any code using firebase/app.

The below example is all you need to configure Google sign-in, assuming your project had already taken the steps necessary to enable it.

The below code uses React. If you're using something else the main changes will be in how you handle the callback auth.listen to store the signed-in user (or null if the user signed out).

// auth.js
import Auth from "firebase-auth-lite";

const auth = new Auth({
apiKey: "YOUR_API_KEY",
redirectUri: window.location.origin,
});
// I spent a while wondering why nothing was working - you need the below to wire up handling of the redirect after signing in
auth.handleSignInRedirect();

const [user, setUser] = React.useState(null);
// This is the callback where you need to store the user details
auth.listen((user) => {
setUser(user);
});

const signIn = async () => {
await auth.signInWithProvider(`google.com`);
};

export { user, signIn };

Migrating code

Creating the database instance is a little different:

- import "firebase/firestore";
- import firebase from "../Firebase/firebase";
+ import Auth from "firebase-auth-lite";
+ import Database from "firebase-firestore-lite";

- const db = firebase.firestore();

+ const auth = new Auth({
+ apiKey: "YOUR_API_KEY",
+ })

+ const db = new Database({ projectId: "YOUR_PROJECT_ID", auth })

We no longer have an instantiated firebase instance from firebase/app, so instead we pass through an auth instance with our apiKey. This doesn't need to be the same instance of auth we configured sign-in with. Because we no longer configure firebase (which would have contained our project id) we also need to pass projectId to the Database constructor.

It is possible to re-use the same auth instance from the auth.js example above, though I prefer to not export and expose that to the rest of my app.

Get a single document

A few notable changes:

  • Collections are referenced by ref rather than collection
    • ref actually allows you to refer to documents or collections
  • Documents in a collection are referenced by child rather than doc
    • You can also use a path string (e.g. /collection/documentId)
  • Documents are returned with their data by default (no need to call data())
- const itemRef = await db.collection("items").doc(id).get();
- const item = itemRef.data();

+ const item = await db.ref("items").child(id).get();
// or
+ const item = await db.ref(`items/${id}`).get();

Get all documents in a collection

The list method is used instead of calling get on a collection. By default the list method only returns a single page of 20 items - so if you want an entire collection you need to pass a large pageSize to the list call.

- const accountsResult = await db.collection("accounts").get();
- const allAccounts = accountsResult.docs.map((d) => d.data());

+ const accountsResult = await db.ref("accounts").list({pageSize: 1000});
+ const allAccounts = accountsResult.documents;

Add, update or delete a document

No changes needed here - the mutate methods all work as they did before (albeit with different patterns to access a collection/individual document).

If using a serverTimestamp (docs) you'll need to import Transform (docs) and use that, rather than the FieldValue.serverTimestamp() from the Firestore SDK:

+ import Transform from "firebase-firestore-lite/dist/Transform"

- const serverTimestamp = firebase.firestore.FieldValue.serverTimestamp();

await itemRef.update({
- updatedAt: serverTimestamp,
+ updatedAt: new Transform("serverTimestamp"),
})

Query documents

A few differences here:

  • Rather than chaining calls to where we pass an array of filters (each of which is an array)
  • We call run on the query instead of get
  • If we want the document's id we need to query the __meta__ property
-  const itemsResult = await db.collection("items")
- .where("reportingDate", ">=", fromDate)
- .where("accountId", "==", accountId)
- .get();

- const items = itemsResult.docs.map((d) => {
- return { ...d.data(), id: d.id };
- });

+ const itemsQuery = db.ref("items")
+ .query({
+ where: [
+ ['reportingDate', '>=', fromDate],
+ ['accountId', '==', accountId]
+ ]
+ })

+ const itemsResult = await itemsQuery.run();

+ const allItems = allItemsResult.map((d) => {
+ return { ...d, id: d.__meta__.id };
+ });

Summary

If you're looking for a reduced bundle size, easier API, are comfortable with an unofficial SDK, and don't need offline or realtime support - then firebase-firestore-lite is worth checking out. I've only scratched the surface of what is supported - read through the documentation for firebase-firestore-lite for a complete overview.

The alpha SDK sounds promising, though who knows when a production-ready release is coming (weeks, months, years?). Until then (and maybe even after if the API is still so clunky) I'll keep reaching for firebase-firestore-lite.

If you're using Firestore for the first time I'd still suggest you use the official SDK. When you need to search for problems (and you undoubtedly will be - the docs and some of the design choices/behaviors are...interesting - see the database is on fire) you'll need to be searching for the right terms/methods/etc.