Data

Schema

Data Schema in FIrebase's Firestore Cloud Database

Our schema consists of many different collections; Firestore is not a relational database and uses collections as opposed to tables. Unique identifiers are used to track users, animals, and other objects. Having said this, we are using Firestore relationally in practice.

Pointers to Collections as Lists of a Certain Type

Bounties Entry in Firestore
Bounty Collection with Single Item

Because we are using initializer data, it may be difficult to see why this is important to include. We created our schema in such a way that it could be easily integrated with Typescript model files:

Bounties Interface
Bounty Interface

The bountiesList variable is a list of objects of type Bounty. In Firestore, we use a list of pointers to the Bounty collection's relevant items.

Relating Items by ID

User Collection

Suppose that any one user has several other files associated with him or her. If this is the case, these other objects have properties containing their owner userId for querying. Take, for example, this user's userId 1234567. If you wanted to find other objects owned by this user (or vice versa -- find the user from an object), you can query the targeted collection of items and filter by userId.

Using Interfaces Dynamically

this.userService.getUser('userId').subscribe((user: User) => {
 this.user = user;
});

Here, we're creating a subscription to the getUser service function and casting the returned object (user) to the User interface type automatically. It's important to remember that observables represent a stream of data associated with other objects, so this is not exclusive to a return of just one object. Every object passed through the observable could be cast to this type. It depends on the return value of the observable.


Firestore

Data imported or exported to Firestore using an interface conversion as well.

import { firestore } from "firebase-admin";

const converter = <T>() => ({
 toFirestore: (data: T) => data,
 fromFirestore: (snap: FirebaseFirestore.QueryDocumentSnapshot) => snap.data() as T
});

...

const animals = firestore().collection('Animal').withConverter(converter<Animal>());

...
animals.get().then((querySnapshot) => {
 const animalsData: Animal[] = [];
 querySnapshot.forEach((doc) => {
    animalsData.push(doc.data());
 });
});

This is an implementation for mapping Firestore data to a specific interface. It starts by defining a converter function that specifies how to convert data to/from Firestore. This converter is then applied to a Firestore collection reference using Firestore's withConverter method. Finally, all documents from the collection are mapped to their specified data type.


Last updated

Was this helpful?