Firestore query where the results have a field that contains a document ref to be passed to a second query to retrieve all the documents

Viewed 73

I have a database structure attempt to deal with the classic events / participants schema. I have events with participants, and participants attend events. I believe for a query that delivers details of all events a participant has attended to scale properly, it is not good practice to query all the events and filter them to find a participant. Comments are welcome on this view. The users are in one collection and the events are in another. So I have added a subEvent subcollection to users to store the document ref for the events they participate in. I now want to retrieve those document references from a query in users and retrieve the detailed event records in the second query in events. Here’s the scheme with the event ids in both.

enter image description here

enter image description here

My first query looks like this:-

const asnapshot = await db.collection("users").doc(_req.params.id).collection("subEvents").get();

And the second:-

const doc = await db.collection("events").doc(results_from_asnapshot).get();

Where results_from_asnapshot is just a placeholder for the document ids from the first. Note I mean ids plural as the first query will return multiple results, and the second one only by one. To deal with this I am attempting to create a result from a snapshot that contains all the events, and then loop though them in the second query to build a complete detailed list of events that the participant has attended. Several problems have emerged that I would like help with. One is the output from the first query that is acceptable in the second, and there seem to be conflicting answers as to what will work see. Some say a string like this will work, and others do not. I had doc.data() is not a function error plaguing me until for no reason, on a test query like this it inexplicably went away.

const doc = await db.collection("events").doc("0adFOpcHZZONGDu3Qw7n").get();

And secondly how to properly loop the second query. And the third issue is why the second query, which is preceded by await, does it warn in the linter that await will be ignored. I know that one query must end before the next can start so why not use await? Here goes my vastly flawed attempt, one of the hundreds that I have tried, you will note it is a node.js firebase function:

const functions = require("firebase-functions");
const express = require("express");

const admin = require("firebase-admin");
admin.initializeApp();

const appWhen = express();
const db = admin.firestore();

appWhen.get("/eventsDetails/:id", async (_req, res) => {
  functions.logger.log("Function started");

const snapshot = await db.collection("users").
doc(_req.params.id).collection("subEvents").get();

const userevents = [];
snapshot.forEach((doc) => {
const id = doc.id;
const data = doc.data().eventID; 
userevents.push({id, ...data});
});
functions.logger.log("userevents", JSON.stringify(userevents));//EDIt Clearer log
const eventlist = [];
userevents.forEach((event) => {
 const doc = db.collection("events").doc(event).get();// Edit removed await
 const id = doc.id;
 const eventData = doc.data();
 functions.logger.log("eventData", eventData);
 eventlist.push({id, ...eventData});
 });
res.status(200).send(JSON.stringify({id: id, ...eventlist}));
});

Any help most welcome.

1 Answers

There are some issues with your code that I will point out.

  1. If you want to read the document in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

    for (const event of userevents) {
       ...
    }
    
  2. If you're using a reference field from the result, you only need to execute the reference and get the data.

    const doc = await event.eventID.get();
    
  3. On this line of code: res.status(200).send(JSON.stringify({id: id, ...eventlist}));, I'm not sure where you're getting the variable id as all the variables id are inside a loop in which you'll get an error.


Here's a sample working snippet for your reference:

const functions = require("firebase-functions");
const express = require("express");

const admin = require("firebase-admin");
admin.initializeApp();

const appWhen = express();
const db = admin.firestore();

appWhen.get("/eventsDetails/:id", async (_req, res) => {
  
  functions.logger.log("Function started");

  const snapshot = await db.collection("users").doc(_req.params.id).collection("subEvents").get();

  const userevents = [];

  snapshot.forEach((doc) => {
    const id = doc.id;
    // renamed to eventID as this is an object of reference.
    const eventID = doc.data().eventID;
    // removed ellipsis as this is an object and doesn't need to use Spread syntax
    userevents.push({id, eventID});
  });

  functions.logger.log("userevents", JSON.stringify(userevents));
  
  const eventlist = [];
  // In here, you can see that I didn't use `.foreach`.
  // `await` will not work using `.foreach`
  // If you want to use `.foreach`, you have to use the Promise `.then` instead.
  for (const event of userevents) {
    // Use the reference and use `get()` to fetch the data.
    const doc = await event.eventID.get();
      if (doc.exists) {
        const id = doc.id;
        const eventData = doc.data();
        eventlist.push({id, ...eventData});
      } else {
        // doc.data() will be undefined in this case
        console.log("No such document!");
      }
  }
  // In here, I'm not sure what ID you want to use as you haven't declared the variable `id` globally
  // So using the variable ID will return an error.
  res.status(200).send(JSON.stringify({id: ??, ...eventlist}));
});
Related