aszet
aszet

Reputation: 93

Firebase query with subcollection

I'm having difficulty pulling the subcollection when I have queried the document using a .where(). I keep getting the error:

TypeError: collectionData.where(...).collection is not a function

If I use a standard .doc() it works, the .where() does not work.

hotels is a subcollection of each doc in the main collection. The hotelCollection is suppose to pass the extracted data through the class to be parsed and executed.

index.js

exports.hotels = functions.https.onRequest((req,res) => {
    cors(req, res, () => {
        const countryCode = req.url.replace('/', '').toUpperCase();
        const hotelCollection = collectionData.where('countryCode', '==', 'VN').collection('hotels');

        switch (req.method) {
            case 'GET':
                Hotels.list(hotelCollection, req, res);
                break;
            case 'POST':
                Hotels.create(hotelCollection, req, res);
                break;
            default:
                res.status(405).send({error: 'An error occurred!'});
                break;
        }
    })
});

hotel.js

let admin = require('firebase-admin');

class Hotels {

    static list(hotelCollection, req, res) {
        let hotelData = [];

        return hotelCollection.collection('hotels').get()
            .then(hotels => {
                hotels.forEach(hotel => {
                    hotelData.push(hotel.data());
                });
                return hotelData;
            })
            .then(hotelData => {
                return res.status(200).send(hotelData);
            })
            .catch(err => {
                return res.status(404).send('Error finding hotel for this country: ' + err);
            });
    }

    static create(hotelCollection, req, res) {
        return hotelCollection.add(req.body)
            .then(result => {
                return res.status(200).send('Hotel has been added!');
            })
            .catch(err => {
                return res.status(404).send('Error adding hotel for this country: ' + err);
            });
    }
}
module.exports = Hotels;

Database enter image description here

Upvotes: 0

Views: 131

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83093

In your Hotels class, in the create method, hotelCollection shall be a CollectionReference, since you call the add() method.

On the other hand, in the list method, since you do hotelCollection.collection('hotels').get(), it means that hotelCollection shall be a DocumentReference (or hotelCollection is equal to admin.firestore(), which does not seem to be the case...).

So, if you want to have the same signature for these two methods, you should pass the CollectionReference corresponding to the hotels sub-collection of the document returned by your query.


So, first, adapt the Hotels class as follows:

class Hotels {

    static list(hotelCollection, req, res) {
        let hotelData = [];

        return hotelCollection.get()  // <-- !!! See change here
            .then(hotels => {
                hotels.forEach(hotel => {
                    hotelData.push(hotel.data());
                });
                return res.status(200).send(hotelData);  // !!! <-- and also here, we removed a then()
            })
            .catch(err => {
                return res.status(404).send('Error finding hotel for this country: ' + err);
            });
    }

    static create(hotelCollection, req, res) {
        return hotelCollection.add(req.body)
            .then(result => {
                return res.status(200).send('Hotel has been added!');
            })
            .catch(err => {
                return res.status(404).send('Error adding hotel for this country: ' + err);
            });
    }
}

Then, call it as follows:

exports.hotels = functions.https.onRequest((req,res) => {
    cors(req, res, () => {

        const countryCode = req.url.replace('/', '').toUpperCase();

        const countryQuery = collectionData.where('countryCode', '==', 'VN');

        countryQuery.get()
        .then(querySnapshot => {
           const countryDocRef = querySnapshot.docs[0].ref;
           const hotelCollection = countryDocRef.collection('hotels');

           switch (req.method) {
              case 'GET':
                  Hotels.list(hotelCollection, req, res);
                  break;
              case 'POST':
                  Hotels.create(hotelCollection, req, res);
                  break;
              default:
                  res.status(405).send({error: 'An error occurred!'});
                  break;
           }

        });

    });
});

You first execute the query, then get the first QueryDocumentSnapshot of the QuerySnapshot through the docs property, then get its DocumentReference, and finally get the document's hotels sub-collection, in order to pass it to the Hotels methods.

Note that above code is based on two assumptions:

  • There is only one document with a given countryCode value, hence the use of docs[0];
  • The collectionData variable holds the collection shown on the "Database" screenshot, i.e. the one on the left part of the screenshot.

Finally, note that if you want to get the value of the countryCode from the URL of the GET HTTPS Request (i.e. replace the hardcoded VN value by a variable value passed through the GET query string parameters), you should use req.query, see https://flaviocopes.com/express-get-query-variables/

Upvotes: 1

Related Questions