If you're looking to add the tagging feature to your app that uses Firestore, then I have good and bad news for you. The good news is that can add basic tag query in minutes, and there are at least a couple ways to do that, but the bad news is that each of these approaches has limitations, and not every scenario is possible.
In this article, we'll look at approaches to each scenario and its limitations.
Let's start with the basic scenario where you need to query all documents that has a specific tag.
You can keep tags as a list of strings and use array-contains
where filter to query it:
db.collection('jobs')
.where('tags', 'array-contains', 'firebase')
You can also easily combine it with ordering condition after adding the required index:
db.collection('jobs')
.where('tags', 'array-contains', 'firebase')
.orderBy('publishedAt', 'desc')
But if you'll try to query collection using multiple
array-contains
:
db.collection('jobs')
.where('tags', 'array-contains', 'firebase')
.where('tags', 'array-contains', 'react')
.orderBy('publishedAt', 'desc')
Then you'll get an error:
FirebaseError: Invalid query. You cannot use more than one 'array-contains' filter.
Luckily, now, Firestore has a solution for that.
If you need to query documents that have at least one of specified tags,
then you can use the latest addition to Firestore,
array-contains-any
where filter:
db.collection('jobs')
.where('tags', 'array-contains-any', ['firebase', 'react'])
Just like array-contains
,
array-contains-any
also could be used with ordering
condition:
db.collection('jobs')
.where('tags', 'array-contains-any', ['firebase', 'react'])
.orderBy('publishedAt', 'desc')
However, if you want to query documents that have all of given
tags, not any, then you are out of luck because there's no
array-contains-all
.
You still can do that, but to make it happen, you'll need to change the way you store the tags.
To query documents that have all of the given tags, you'll need to change the way you store those. Instead of the array of strings, you'll need to use the map:
[
'typescript',
'firebase',
'react'
]
// →
{
typescript: true,
firebase: true,
react: true
}
So that you could query using regular where filter:
db.collection('jobs')
.where('tags.firebase', '==', true)
.where('tags.react', '==', true)
This way, you can query simultaneously by any number of tags.
However, if you would try to sort the result:
db.collection('jobs')
.where('tags.firebase', '==', true)
.where('tags.react', '==', true)
.orderBy('publishedAt', 'desc')
Then you'll get an error:
FirebaseError: The query requires an index.
To make it work you'll have to add compound index on
publishedAt
, tagsMap.react
and
tagsMap.firebase
which means you'll need to add index to all
possible combinations making it virtually impossible.
The only option is to implement sorting on the client-side unless you have too many documents in your collection to make it feasible. If so, I'm afraid you're out of options to expect that use running a server with an Elastic Search instance (or something similar) or using SaaS service such as Algolia.
Tag filtering with Firestore is an easy task unless you need to query documents that have all of the given tags. Then you'll have to choose between client-side sorting and running an instance with a search engine or using 3rd-party service.