Deleting nested data in Firestore

*Thinking of building a platform on Firestore? Check-out our 2023 recommendations on the platform

Deleting nested data in Firestore is frustrating. It's somehow unsupported natively, and there doesn't seem to be an elegant way of doing it.

Firestore

Firestore

We’ve build a lot of platforms on Firebase. Like Parse, it’s a backend as a service (BaaS) that can save a lot of development time. It provides a suite of cloud services out of the box.  For example, you can:

  • Store your data in a Firestore database
  • Create API endpoints with Cloud Functions
  • Send push notifications to mobile phones
  • House your assets with Firebase Storage
  • Track user behavior with analytics
all under its platform. In fact, if you want to implement push notifications on Android, you must use a Firebase project. Nonetheless, you also must deal with some annoying quirks:
  • For one thing, you're beholden to Google.
  • Unlike Parse, there's no way to develop locally.
  • Support for testing is minimal.
  • Firestore has many structural quirks.
We understand the need to make tradeoffs, of course. But firestore lacks a "not equal" query operator, the source of many headaches. Querying relationships can be a nightmare, too.  Because you can only submit one query at a time (except on the admin platform), it's difficult to efficiently work with many-to-many models.  Worst of all, Firestore doesn't support deleting nested data. In other words, you can be left with a lot of orphan data if you're not careful.

Deleting nested data

We write many tests that touch Firestore databases.  After each test, the database should be completely empty. Even in production, we often need to delete entire collections at once.  Removing each object individually is a non-starter. Apparently other people often run into this problem too. The following snippet accomplishes the task cleanly.

const clearCollection = (db) => async (collections) => {
  await Promise.all(
    collections.map(async (collection) => {
      // get documents
      const documents = await collection.listDocuments()
      await Promise.all(
        documents.map(async (doc) => {
          const collections = await db.doc(`${doc.path}`).listCollections()
          //           If document has a sub collection delete that first
          if (collections.length > 0) {
            await clearCollection(db)(collections)
          }
          await doc.delete()
        })
      )
    })
  )
}

const clearDB = async (db) => {
  // get root collections
  const collections = await db.listCollections()

  // delete root collections
  return clearCollection(db)(collections)
}

module.exports = { clearDB, clearCollection }

// clearDB(admin.firestore()) will delete everything in your database
// clearCollection(admin.firestore())(collectionRef) will delete everything within a collection

Recursion is effective since the Firestore database resembles a tree. Whenever we start a new project, we’re sure to drop this utility in.