Firestore on Node in Production. Part 2: Collection paths
This is the second part of my series about how we use and structure Firestore in our projects. If you’re not familiar with firestore-storage and haven’t read the first part about repositories you should head over and give it a glance.
This part is about how to define your collection paths at a single location and use them not only in your backend and frontend code, but also as I will describe in future articles in test cases for rules and in typed index definitions. Places to use them:
- At the backend when defining repositories
- Creating Firestore triggered functions
- Web based frontend application
- Rules tests
- Defining indexes using IndexManager
To define a path you use the CollectionUtils
class as follows:
CollectionUtils.createPath('restaurants/{restaurantId}/reviews/{reviewId}');
To keep it all under one namespace create a file collections.ts
and a class called Collections
. Add static variables for each collection you have.
export class Collections {
static Users = CollectionUtils.createPath('users/{userId}')
static Restaurants = CollectionUtils.createPath('restaurants/{restaurantId}');
static Restaurant_Reviews = CollectionUtils.createPath('restaurants/{restaurantId}/reviews/{reviewId}')
}
Usage
CollectionUtils.createPath()
returns a function which takes ids as its arguments. Each of the variables above now allows you to generate the full path to either a collection or a document.
const collectionPath = Collections.Restaurants_Reviews('starbucks');
console.log(collectionPath);
// restaurants/starbucks/reviewsconst docPath = Collections.Restaurants_Reviews('starbucks', 'review123');
console.log(docPath);
// restaurants/starbucks/reviews/review123
We can now improve the repository we created in our last part by using those paths instead of hardcoding it into the repository.
export class ReviewRepository extends BaseRepository<Review> {
getCollectionPath(...documentIds: string[]): string {
return Collections.Restaurant_Reviews(...documentIds);
}
}
Note I removed the check if enough parent ids are passed to generate the path. The path function itself now handles that. So calling Collections.Restaurant_Reviews()
throws an error because at least the restaurant id is required.
Firestore triggered Firebase function
The path function has an additional properties just called path
which returns the original path with the id placeholders. This can be used to create a Firebase function which listens to Firestore writes.
console.log(Collections.Users.path)
// users/{userId}export const updateUserInfo: CloudFunction<Change<DocumentSnapshot>> = functions.firestore
.document(Collections.Users.path)
.onWrite(async (change, context) => {
console.log(context.params.userId);
});
Frontend usage
A quick side note when you use those paths in an Angular or React application you should import the CollectionUtils
directly from its file location instead of the package root to avoid having dependency problems.
import {CollectionUtils} from "firestore-storage/dist/lib/storage/collection_utils";
Conclusion
Awesome. You now have a central location where you manage your paths. In future parts there will be a few more places where this will come in handy.
If you have any questions or problems please don’t hesitate to leave a comment here or open an issue over at the Github repo.