FireStore create a document if not exist - node.js

I want to update a doc like this:
db.collection('users').doc(user_id).update({foo:'bar'})
However, if the doc user_id does not exists, the above code will throw an error.
Hence, how to tell Firestore to create the student if not exists, in other word, behave like this:
db.collection('users').doc(user_id).set({foo:'bar'})

I think you want to use the following code:
db.collection('users').doc(user_id).set({foo:'bar'}, {merge: true})
This will set the document with the provided data and will leave other document fields intact. It is best when you're not sure whether the document exists. Simply pass the option to merge the new data with any existing document to avoid overwriting entire documents.
For more detailed about managing data with firestore check this link

If you need things like created and updated timestamps, you can use this technique:
let id = "abc123";
let email = "john.doe#gmail.com";
let name = "John Doe";
let document = await firebase.firestore().collection("users").doc(id).get();
if (document && document.exists) {
await document.ref.update({
updated: new Date().toISOString()
});
}
else {
await document.ref.set({
id: id,
name: name,
email: email,
created: new Date().toISOString(),
updated: new Date().toISOString()
}, { merge: true });
}
This will create the document if it doesn't exist with created and updated timestamps, but only change the updated timestamp if it exists.

Related

findByIdAndUpdate adding another document to the database rather than updating

I am using MEAN stack for patient CRUD operations. The update does not seem to be working properly. It adds another document to the database with the updated info but with a null id and leaves the old document that is supposed to be updated as is.
below is the code I wrote in the service for update patient
editPatient(id:string,patient: Patient){
const headers = { 'content-type': 'application/json'}
const body=patient;
console.log(body)
let url=environment.PATIENT_BASE_URL+environment.PATIENT.UPDATE_PATIENT + "?userId=" +id;
return this.httpClient.put(url, body);
}
Those are the contents of the environment file
export const environment = {
production: false,
BASE_URL:'http://localhost:3000',
PATIENT_BASE_URL:'http://localhost:3000/patients/',
PATIENT:{
GET_ALL_PATIENTS: 'list',
GET_PATIENT: 'view',
UPDATE_PATIENT: 'update',
DELETE_PATIENT: 'delete',
SEARCH_PATIENT: 'search',
ADD_PATIENT: 'add',
}
};
This is the code in patients.js
router.put('/update', function(req, res, next) {
const userId = req.body.userId;
let firstnameVal = req.body.firstName;
let lastnameVal = req.body.lastName;
let usernameVal = req.body.username;
let emailVal = req.body.email;
let birthDateVal = req.body.birthDate;
let genderVal = req.body.gender;
let patientObj = {
firstName: firstnameVal,
lastName: lastnameVal,
username: usernameVal,
email: emailVal,
birthDate : birthDateVal,
gender: genderVal
};
// patientsModel.update({'gender':'female'}, )
patientsModel.findByIdAndUpdate(userId, patientObj,{upsert: true, new: true} ,function(err, patientResponse){
if(err){
res.send({status:500, message: 'Unable to update the patient'});
}
else{
res.send({status:200, message: 'User updated successfully' ,results: patientResponse});
}
});
});
Because you used this option
upsert: true
If item with id not found it creates a new document
you can read the docs here
Using the upsert option, you can use findOneAndUpdate() as a
find-and-upsert operation. An upsert behaves like a normal
findOneAndUpdate() if it finds a document that matches filter. But, if
no document matches filter, MongoDB will insert one by combining
filter and update as shown below.
The second argument to findByIdAndUpdate is an update object. If it does not contain any update operators, it is treated as a replacement document.
If your intent is to replace the entire document so the only fields it contains are the ones provided in this function, add the _id to the object:
let patientObj = {
_id: new mongoose.types.ObjectId(userId),
firstName: firstnameVal,
...
If the intent is to modify the provided fields but leave any others fields alone, use the $set update operator like
patientsModel.findByIdAndUpdate(userId, {"$set": patientObj}, ...
There are a few problems with your code - as others have said:
the use of upsert: true is suspicious - I can't imagine when you'd want to upsert this, and
the lack of $set is also unusual unless the patientObj represents the entire document you wish to set
both these items are causing you issues, but I suspect your main problem is actually that your ID doesn't match anything.
You mention an auto-generated ID. Mongo uses an ObjectId (though mongoose perhaps does not) - depending on how you serialise this value, the string representation of it would probably look like this: 63b310df2b36d95e156a237d - however when you query for that value (as you do with userId) - it will return no matches, since you need to convert it to an object ID:
userId = new mongoose.types.ObjectId(req.body.userId)
You should also fix items 1 and 2 above.

Best practice way to update a parent document field that is computed from sub collection documents in google cloud firestore

Here's an abbreviated version of the data model for my firestore collections:
// /posts/{postId}
interface Post {
id: string;
lastCommentedAt: Timestamp | null
}
// /posts/{postId}/comments/{commentId}
interface Comment {
id: string;
createdAt: Timestamp;
}
So I have a collection of posts, and within each post is a subcollection of comments.
I want to do the following:
When a comment is created, update the lastCommentedAt field of the parent Post document with the comment's createdAt value
When a comment is deleted, the parent Post's lastCommentedAt field may no longer be valid so we need to get all of the Post's comments and get the most recent comment to update lastCommentedAt.
I see a couple ways to do this:
In my client code, I can do the above logic inside of functions like createComment(post, comment), and deleteComment(post, comment)
Especially in the case of deleting it seems like it is not ideal to require the client to fetch all comments for the post and iterate through them just to delete one
Would I need to use transactions for this since someone could be deleting a comment at the same time someone was creating a new one?
In my cloud functions I could create triggers on /posts/{postId}/comments/{commentId} for create and delete and do this logic on the backend
Is there risk of race conditions here as well? Again maybe I should use a transaction?
The use case for this lastCommentedAt field is that I want to be able to query for posts and sort them by the ones that have recent comments.
Edit: possible implementation for deleteComment using a batched write. Is it actually safe to do a query for documents before the writes though?
async function deleteComment(post, comment) {
const batch = writeBatch(firestore);
const postRef = doc("posts", post.id);
const commentRef = doc("posts", post.id, "comments", comment.id);
const commentsCollection = collection("posts", post.id, "comments");
const recentCommentSnapshot = await getDocs(
query(
commentsCollection,
where("id", "!=", comment.id),
orderBy("createdAt", "desc"),
limit(1)
)
);
let lastCommentedAt = null;
if (recentCommentSnapshot.docs.length > 0) {
lastCommentedAt = recentCommentSnapshot.docs[0].data().createdAt;
}
batch.delete(commentRef);
batch.update(postRef, { lastCommentedAt });
await batch.commit();
}
For adding comments you can use batched writes to ensure the comment is added and the parent document is update with current timestamp.
const batch = db.batch();
const postDocRef = db.collection('posts').doc('postId');
const commentRef = postDocRef.collection("comments").doc();
batch.update(postDocRef, { lastCommentedAt: FieldValue.serverTimestamp() });
batch.set(commentRef, { createdAt: FieldValue.serverTimestamp() });
await batch.commit();
When deleting comments you might have to query the latest comment using createdAt field and then update parent document.
const getLastComment = (postId: string) => {
const postRef = db.collection('posts').doc(postId);
return await postRef.orderBy('createdAt', 'desc').limit(1).get();
}

How to generate a unique 6 digits number to use as an ID for documents in a collection?

I have a collection of documents which are being added as a result of users' interactions.
Those docs already have an _id field, but I also wanna add a unique human readable ID for every existing and newly created object, in a form of D123456
What is the best way of adding such an ID and being sure that all those IDs are unique?
MongoDB doesn't have an auto-increment option like relational databases.
You can implement something yourself: before you save your document, generate an ID. First, create a database collection whose sole purpose is to hold a counter:
const Counter = mongoose.model('Counter', new mongoose.schema({
current: Number
}));
Second, before you save your object, find and increment the number in the collection:
const humanReadableDocumentId = await Counter.findOneAndUpdate(
// If you give this record a name, you can have multiple counters.
{ _id: 'humanReadableDocumentId' },
{ $inc: { current: 1 } },
// If no record exists, create one. Return the new value after updating.
{ upsert: true, returnDocument: 'after' }
);
const yourDocument.set('prettyId', format(humanReadableDocumentId.current));
function format(id) {
// Just an example.
return 'D' + id.toString().padStart(6, '0');
}
Note: I've tested the query in MongoDB (except for the 'returnDocument' option, which is Mongoose-specific, but this should work)
Formatting is up to you. If you have more than 999999 documents, the 'nice looking ID' in the example will just get longer and be 7+ characters.

How to add new field and update existing field simultaneously in cloud firestore?

I want to add a new field in a document but the field doesn't exist and update the existing field simultaneously, I tried using update and set both but none of them adds a new field although it works to update the existing field.
const userDocumentReference = admin.firestore().collection('users').doc(data['uid']);
await userDocumentReference.update({
'isVerified': 'pending', // old field
'selfieUrl': data['selfieUrl'] // new field
}, { merge: true });
To update an existing Firestore document, using the update() method, as follows should work:
const userDocumentReference = admin.firestore().collection('users').doc(data['uid']);
await userDocumentReference.update({
'isVerified': 'pending', // old field
'selfieUrl': data['selfieUrl'] // new field
});

Mongoose Append in subdocument or inside the array in the subdocument itself

This is how my Mongoose db looks like
_id: ObjectId("5c7d6b0b54795c02a6a5cb16")
people:[
{
_id: ObjectId('7c7d6b0b54795c02a6aa878')
name: John
bizs:[
{name:"Shop A"},
{name:"Shop B"}
]
},
{
_id: ObjectId('7c7d6b0b54795c02638b9cd')
name: Mary
bizs:[
{name:"Shop X"}
]
}
]
If I add a person who is already in the database (say for example John) with his business, I need the business to append itself in the bizs array. Eg Shop A,ShopB, Shop C.
But if I add another person who is not in the database (like how I have added Mary) I need it to create a whole new object in the people array.
I have tried doing this with upsert but it does'nt give me what I want.
How do I get something like this?
You first need to find if there exist a person with name "John"(as per your example),this can be done using a simple find query.if the result of that query is null or undefined then you can insert a new object(a new record into people array) else you can append it to the found document and the save it.
const name = req.body.name;
Schema.findOne({"people.name" : name} , (err , user)=>{
if(err){
//handle
}
if(!user){
//create a new record and push it to people array and save it.
}
else{
//create a record(with your object) and push it to people array and save
user.people.push();
user.save()
}
});

Resources