
Handling Firestore Timestamps
5 min read
In Firestore, there are two ways to deal with timestamps safely. If you pass a regular date value on the client side, it can be tampered with. We want our data to be secure.
Firestore Functions
I don't recommend this version, but I am posting it for reference. You should usually just use Firestore Rules (keep reading). I created a Universal Function a while back, which Could and Should be simplified:
exports.myFunction = functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change, context) => {
// the collections you want to trigger
const setCols = ['posts', 'reviews','comments'];
// if not one of the set columns
if (setCols.indexOf(context.params.colId) === -1) {
return null;
}
// simplify event types
const createDoc = change.after.exists && !change.before.exists;
const updateDoc = change.before.exists && change.after.exists;
const deleteDoc = change.before.exists && !change.after.exists;
if (deleteDoc) {
return null;
}
// simplify input data
const after: any = change.after.exists ? change.after.data() : null;
const before: any = change.before.exists ? change.before.data() : null;
// prevent update loops from triggers
const canUpdate = () => {
// if update trigger
if (before.updatedAt && after.updatedAt) {
if (after.updatedAt._seconds !== before.updatedAt._seconds) {
return false;
}
}
// if create trigger
if (!before.createdAt && after.createdAt) {
return false;
}
return true;
}
// add createdAt
if (createDoc) {
return change.after.ref.set({
createdAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true })
.catch((e: any) => {
console.log(e);
return false;
});
}
// add updatedAt
if (updateDoc && canUpdate()) {
return change.after.ref.set({
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true })
.catch((e: any) => {
console.log(e);
return false;
});
}
return null;
});
This function automatically adds the createdAt
and updatedAt
timestamps to the documents in a the collections you specify. I honestly would not recommend using this, as Universal Functions can be more costly than specifying the collections you want to use. Nevertheless, you could see the pattern here to get the results you want.
Also, look at my old adv-firestore-functions repo if you want more ideas.
Firestore Rules + serverTimestamp
This is by far the better way. Firestore Functions will incur more reads and writes than necessary. What is better, is to simply check on the security with Firestore Rules as you add the data.
import { serverTimestamp } from 'firebase/firestore';
...
await setDoc(doc(db, "some-doc")), {
something: 'here',
updatedAt: serverTimestamp()
});
Notice I do not use new Date()
, but instead serverTimestamp()
;
Then, just make sure your Firestore rules match the case:
allow create: if request.time == request.resource.data.createdAt;
allow update: if request.time == request.resource.data.updatedAt;
This is enforce proper timestamps. If you try to add an incorrect date, it will fail.
J