Search code examples
firebasegoogle-cloud-firestorefirebase-authenticationgoogle-cloud-functions

firebase blocking function (beforeUserCreated) to save user record into firestore with unique field


I have a requirement to insert an user record with additional properties into firestore collection when beforeUserCreated blocking function is called. The user record contains an unique field which must be 16 digits numeric value generated based on epoch miliseconds and a random value. How do I ensure the record is unique across all the user records in the collection when saving into firestore and return error if duplicate value detected?

Note:

  1. Using firebase auto id function to generate the unique value is not an option as the unique field value must be 16 digits
  2. Using mobile app to save the user record with firestore security rules is not an option as the user record must be saved in blocking function

Solution

  • The same document id can't be exists more then one in same collection right? So by create another document with that 16 digits numeric as document id, Then batch write with record like:

    const batch = defaultFirestore.batch();
    
    const recordRef = defaultFirestore.collection("records").doc(userId);
    batch.create(recordRef, record);
    
    const uniqueDigitsRef = defaultFirestore.collection("uniqueDigits").doc(/*16 digits numeric value*/);
    batch.create(uniqueDigitsRef, emptyData);
    
    return batch.commit();
    

    The document says what .create do is

    Create a document with the provided object values. This will fail the batch if a document exists at its location.

    So if 16 digits are already exists, This batch write would fail, Then generate new one for that record and write to database again.

    Edited:

    Second approach is use transaction, by use transaction, run a query with .get to search any document with same value exists, If not create record for user.

    firestore.runTransaction(transaction => {
      // Generate 16 digits numeric value here
      let query = collection("records").where(/*unique field*/, "==", /*16 digits numeric value*/);
      return transaction.get(query).then(doc => {
        if (!doc.empty) {
          const recordRef = defaultFirestore.collection("records").doc(userId);
          transaction.create(recordRef, record);
        }
      });
    });
    

    If you using .get on non-existent document, to existent, the transaction should retry, Even it's query I did expect that works same, See this answer.

    So if functions trigger closely, and the first one create the same value as another one, The second one should retry.