0

I am completely new to Firebase, maybe I just cannot search for the right keywords, but here's what I'd like to do:

I would like to read a document from the database by id, but only if that document has a specific property set. To be more specific, this would be something like a tiny blog server, where I want to read a post's data, but only if its "published" property is set to true. I don't want unpublished posts to end up on the client side.

I am using the JavaScript SDK, but can only find examples of lookup by either ID or properties...

Is this even possible?


UPDATE: What I have now is (this is Firestore):

firebase.firestore().collection('blog-posts').doc(id).get()

and then later check for the property on the client side:

if (data && data.published === "true")

I would like all the filtering to happen on the server side.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
TamasGyorfi
  • 587
  • 1
  • 6
  • 14
  • Firestore or Realtime db? Can you post the relevant code you're currently using? Here's how to do compound queries in firestore: https://firebase.google.com/docs/firestore/query-data/queries#compound_queries –  Nov 19 '20 at 19:30
  • "I don't want unpublished posts to end up on the client side." While Doug’s answer gives you the solution to write your query, be aware that it would be very easy for a user to read the posts that are not published just by removing the second where clause. In other words, if you want unpublished docs to be « un findable » (and not just filtered out by the query), you should, in parallel to the query, use a security rule. – Renaud Tarnec Nov 19 '20 at 20:36

3 Answers3

3

If you want to get a document only if a field has a certain value, you won't be able to use get() on its DocumentReference. You will have to perform a query on the collection using both the document ID and field as filters on that query like this, using FieldPath.documentId():

const qsnap = await firebase.firestore()
    .collection('blog-posts')
    .where(firebase.firestore.FieldPath.documentId(), "==", id)
    .where("published", "==" true)
    .get()

This yields a QuerySnapshot, which you will have to check if the document was provided.

if (qsnap.docs.length > 0) {
    const doc = qsnap.docs[0];
}

Note that it still costs 1 document read to perform this query even if the document isn't returned.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Oh, wow this worked out of the box. Thanks, that ticks all the boxes for me! – TamasGyorfi Nov 19 '20 at 20:17
  • 1
    Be very careful using this response - the FieldPath.documentId() used this way will **ONLY WORK IN TOP-LEVEL COLLECTIONS**. It doesn't *actually* return the documentId; it returns the full path to the document, including all higher-level collections and documents. See answer below. – LeadDreamer Nov 19 '20 at 21:46
0

You can use where function to filter data on the server-side.

firebase.firestore().collection('blog-posts').where('published', '==', 'true').where(firebase.firestore.FieldPath.documentId(), '==', id).get()
Prime
  • 2,809
  • 1
  • 7
  • 23
  • Unfortunately I need to read one specific post by its ID. (I don't want a post to be readable when somebody guesses the doc ID) – TamasGyorfi Nov 19 '20 at 19:47
0

Firestore does not easily let you filter/select on documentID; among other issues, FieldPath.documentId() returns the FULL PATH of the document, NOT just the id; see this question/answer for a fairly lengthy explanation of what is happening:

Firestore collection group query on documentId

it is possible to also copy the Id into a field in the document, and query by that (and I have my Firestore wrapper automatically helping with that) - BUT - this quickly become what I've seen called an "X-Y" question - asking "How do I do Y?" only because you already assume it's the answer to "How do I do X?"

The question is why do you want to query by the document ID? Is this ID a proxy for some other information (such as a way to identify the user)? If so, use the User ID as a field. In general, the documentId is more about Google Firestore efficiently sharding their database for performance than as a real identifier. Since the documentId is assumed to be unique (or, as the answer referenced explains, the full path to the document is unique), it's most often less useful for finding.

An analogy: using a documentId to find a document is like using a Municipal Parcel Number to designate an address. Sure it's unique, but "Division 46675 Tract 32567 Parcel 12344" is a lot less useful than "450 Lazy Deer Road, Winnetka, New Jersey".

So: in your case, ask yourself: where did you get that documentId from? What does it represent? Can I store that in my document instead so I can use a compound query?

firebase.firestore().collection('blog-posts').where([whatever query identifies the document or documents]).where('published', '==', 'true').get()

Note whether your first condition identifies one or identifies multiple documents, the response object will include an ARRAY, which may have one or more documents in it.

The best general advice for Firestore and other NoSQL databases is to spend the time identifying exactly how you want to use your data - i.e. your queries - and build your structure to make that easier. Remember, Firestore should be a tool to make your life easier, not to make you a slave to Firestore.

LeadDreamer
  • 3,303
  • 2
  • 15
  • 18