Firestore Façade
A clean, strongly-typed, zero-dependency API for Firestore Typescript projects.
NOTE: This project is still in its infancy, and probably not ready for
consumption just yet
Motivation
- Reduce boilerplate code and improve readability
- Provide strict typing on collection methods like
set
and update
- Provide strict typing on query select statements and their partial response
documents
The aim is to keep the API as close to native as possible while providing as
much type safety as is still practical. Firestore Façade is a fairly thin
wrapper which does not prevent you from using using plain Firestore API methods.
For that reason it is also not aiming to cover the full Firestore API surface.
At the moment this library focusses solely on Node.js using the
firestore-admin client.
It should be possible to make this compatible with the Cloud
Firestore with a small
modification, because both are technically the same product.
In the future I would like to add a package addressing the web client and
another one specifically for React hooks.
TODO v1.0
Install
npm install firestore-facade
npm install --save-dev firestore-facade-cli
Configure
In your repository, create a configuration file. It can be named anything and
place anywhere. In this file create a default export object using a root
and optionally a sub
property.
In the root property you list all the Firestore root collections using the name
as key, and map their document type using a placeholder object as shown below:
export default {
root: {
athletes: {} as Athlete,
sports_events: {} as Event,
},
sub: {
athletes: {
medals: {} as Medal,
},
},
};
Subcollections are place under sub
. Currently one level of nesting is
supported.
In this example the collection names in Firestore are snake-cased, but if your
names are camel-cased the key names should mirror that.
The empty objects are simply placeholders to make the types available at
runtime. @TODO explain in detail.
Generate Facade Factory Function
The next step is to generate the facade code by passing the location of the
configuration file:
npm exec generate-facade ./src/my-document-types-config.ts
This should create a file named facade.ts
containing the facade factory
function in the same location as the supplied config file.
Whenever you change something about your collections or their document types,
simply re-run this command to update the facade function.
Usage
Now you can use the facade factory to wrap your instance of the Firestore
client.
import { createFacade } from "./facade";
const db = createFacade(firestore);
Below is an example showing the different API methods.
You can find the complete source code in the nodejs example
package
@TODO document API in detail.
const ref = await db.athletes.add({
name: "Joe",
age: 23,
skills: { c: true, d: ["one", "two", "three"], tuple: ["foo", 123] },
});
console.log(`Stored new document at collection_a/${ref.id}`);
await db.athletes.set(ref.id, {
name: "Jane",
age: 26,
skills: { c: true, d: ["one", "two", "three"], tuple: ["foo", 456] },
});
await db.athletes.update(ref.id, {
age: 27,
"skills.c": true,
"skills.tuple": ["bar", 890],
updated_at: serverTimestamp(),
});
const doc = await db.athletes.get(ref.id);
console.log(doc.data);
const { id: eventId } = await db.sports_events.add({
name: "Olympics",
year: 2045,
});
await db.athletes.sub(ref.id).medals.add({
event_id: eventId,
type: "gold",
});
const fullDocs = await db.athletes.query((ref) =>
ref.where("skills.c", "==", true),
);
console.log(`Retrieved ${fullDocs.length} documents`);
const partialDocs = await db.athletes.queryAndSelect(
(ref) => ref.where("updated_at", "<", new Date()),
["name", "skills"],
);
partialDocs.forEach((doc) => console.log(doc.data.name, doc.data.skills));