0

To create a reusable function and to allow easy replacement of database backends, I'd like to write a simple abstraction layer for Google Firestore.

However, there's the problem that I can't programmatically daisy-chain where conditions. Is there a way to pass in an array of where conditions and pass them to Firestore when executing the query somehow?

This is my current approach, but it doesn't work. The conditions are simply ignore and the first result is returned.

export const getOne = async (
  req: any,
  entity: string,
  select: string[] = [],
  where: any[] = [],
) => {
  // Get entity reference from firestore.
  const entityRef = req.firebaseServer.firestore().collection(entity);
  // Select fields.
  const query = entityRef.select(...select);
  // Add where conditions.
  where.forEach((condition) => {
    query.where(...condition);
  });
  // Limit to 1 result.
  query.limit(1);
  // Run query.
  return await query.get();
};

Example call:

return await getOne(
  req,
  'item',
  ['title', 'language', 'uuid'],
  [
    ['categoryId', '==', req.params.categoryId],
    ['language', '==', req.params.lang],
  ],
);
Thomas Ebert
  • 447
  • 6
  • 15

1 Answers1

0

So, after writing up this question, I found the answer through this thread Firestore: Multiple conditional where clauses

A bit counter-intuitive, where, select etc. don't manipulate the CollectionReference, but return a new modified CollectionReference instead.

where(fieldPath, opStr, value) returns Query

Creates and returns a new Query with the additional filter that documents must contain the specified field and the value should satisfy the relation constraint provided.

https://cloud.google.com/nodejs/docs/reference/firestore/0.10.x/CollectionReference#where


Following that, the working code is:

export const getOne = async (
  req: any,
  entity: string,
  select: string[] = [],
  where: any[] = [],
) => {
  // Get entity reference from firestore.
  let query = req.firebaseServer.firestore().collection(entity);
  // Select fields.
  query = query.select(...select);
  // Add where conditions.
  where.forEach((condition) => {
    query = query.where(...condition);
  });
  // Limit to 1 result.
  query = query.limit(1);
  // Run query.
  return await query.get();
};
Thomas Ebert
  • 447
  • 6
  • 15
  • 1
    For syntactic sugar: Take a look at `Array.reduce`, it allows you to write the loop without changing outer state: `query = where.reduce((query, condition) => query.where(...condition));` – Lucas S. Jul 20 '18 at 14:10
  • Thanks, this looks nicer, @LucasS! However, keep in mind, that you need to pass `query` as the `initialValue` to the reduce function, because otherwise it will assign the first item of the `where` array to the initial `query`. So it would be: `query = where.reduce((query, condition) => query.where(...condition), query);` – Thomas Ebert Jul 26 '18 at 14:02