https://github.com/phamngocduy98/npm-flashstore-admin
Firebase Cloud Firestore Library | For NodeJS server with FirebaseAdminSdk
https://github.com/phamngocduy98/npm-flashstore-admin
firebase firebase-admin firebase-admin-sdk firebase-database firebase-firestore firebase-nodejs nodejs nodejs-framework
Last synced: 1 day ago
JSON representation
Firebase Cloud Firestore Library | For NodeJS server with FirebaseAdminSdk
- Host: GitHub
- URL: https://github.com/phamngocduy98/npm-flashstore-admin
- Owner: phamngocduy98
- Created: 2020-08-04T11:57:11.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-01-07T04:57:29.000Z (over 3 years ago)
- Last Synced: 2025-09-14T13:23:32.275Z (9 months ago)
- Topics: firebase, firebase-admin, firebase-admin-sdk, firebase-database, firebase-firestore, firebase-nodejs, nodejs, nodejs-framework
- Language: TypeScript
- Homepage:
- Size: 297 KB
- Stars: 2
- Watchers: 1
- Forks: 1
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Flashstore admin
**WARNING: Flashstore is currently in early beta version which is under development, may cause unexpected behaviors and should only be used in personal project.**


[](https://codecov.io/gh/phamngocduy98/npm-flashstore-admin)





A firebase firestore library that making it easier to CRUD data with typescript
```
npm i @phamngocduy98/flashstore-admin
```
## How to use
### 1. Define document data type
Define properties of your document by extending `DocumentData` class
```typescript
import {DocumentData} from "@phamngocduy98/flashstore-admin";
export class User extends DocumentData {
constructor(public name: string, public avatarUrl: string) {
super();
}
}
```
### 2. Define collections:
Define your database by extending `Database` class. There you can define your collections using `@Collection` decorator:
`@Collection(DocDataType: D extends DocumentData, collectionName?: string)`
For example, you have a root collection whose name is `users`
```typescript
import {Database, Collection, FirestoreCollection} from "@phamngocduy98/flashstore-admin";
import {User} from ".";
export class MyDatabase extends Database {
@Collection(User, "users")
public users!: FirestoreCollection;
}
```
If you don't define collection's name parameter like `@Collection(User)`, the library will use property's name by default.
You can define sub-collection of a document too. If you have a subcollection `wells` inside a `Village` document, then your Village class should look like this:
```typescript
import {DocumentData, Collection, FirestoreCollection} from "@phamngocduy98/flashstore-admin";
import {Well} from "./sample_db/Well";
export class Village extends DocumentData {
@Collection(Well, "wells")
public wells!: FirestoreCollection;
}
```
### 3. CRUD:
Your need initialize your firebase app before using firestore:
```typescript
import * as admin from "firebase-admin";
import {FirestoreDocument, FirestoreCollection} from "@phamngocduy98/flashstore-admin";
import {User, MyDatabase} from ".";
admin.initializeApp();
const db = new MyDatabase(admin.firestore());
```
#### 3.1 CRUD a collection:
##### QUERY:
```typescript
const userCollection: FirestoreCollection = db.users;
const users: User[] = await db.users.query((ref) => ref.where("name", "==", "Duy"));
````
##### CREATE:
```typescript
// return FirebaseDocument instance of newly create document
const newDocWithAutoCreatedId: FirestoreDocument = await db.users.create(undefined, new User());
const newDocWithSpecificId: FirestoreDocument = await db.users.create("new_user_id", new User());
// create in batch:
const batch = db.batch();
db.users.createInBatch(batch, "new_user_id", new User());
```
##### DELETE:
```typescript
await db.users.delete("new_user_id");
// delete in batch:
const batch = db.batch();
db.users.deleteInBatch(batch, "new_user_id");
```
#### 3.2 CRUD a document:
You need FirestoreDocument instance to read data or make changes to the document.
Get a document instance by ID: `Collection.document(docId)`
```typescript
const userCollection: FirestoreCollection = db.users;
const userDoc: FirestoreDocument = db.users.document("my_user_id");
```
Then you can read or modify to document as you want:
##### READ:
```typescript
// (return null if document not exists)
const userData: User = await userDoc.get();
console.log(userData?.name, userData?.avatarUrl);
```
##### UPDATE:
```typescript
await userDoc.update({avatarUrl: "new avatar"});
```
##### DETELE:
```typescript
await userDoc.delete();
```
##### SET:
```typescript
// (set will overwrite current value or create a new document if the document not exist)
userDoc.set({name: "new name", avatarUrl: "new avatar"});
```
##### Access sub-collection:
```typescript
const villageDoc = await db.villages.document("test_village");
const wellSubCollection: FirestoreCollection = villageDoc.collection("wells");
// Then you can use all methods that is available for collection
await wellSubCollection.create(undefined, new Well("well 1"));
```
---
### 4. Using references.
#### 4.1 Define a document property reference to other Document
For example, you have a Village entity then your village have an owner which is a user. You need create an `owner` property with `@RefFDocument(collectionName: string)` decorator.
```
@RefFDocument("users")
owner: FirestoreDocument;
```
Then the `owner` property will be stored as `DocumentReference` in firestore, while you can access it as a true FirestoreDocument in Village instance.
Make sure your collection name (eg. "users") should be root collection (not a sub-collection).
```typescript
export class Village extends DocumentData {
@RefFDocument("users")
owner: FirestoreDocument;
constructor(public name: string, public description: string, owner: FirestoreDocument) {
super();
this.owner = owner;
}
}
```
##### Read data of referenced document:
Read Village.owner document data:
```typescript
const villageDoc: FirestoreDocument = db.villages.document("village_id");
const villageData: Village = await villageDoc.get();
const ownerDoc: FirestoreDocument = village!.owner;
const ownerData: User = await village!.owner.get();
console.log(ownerData?.name, ownerData?.avatarUrl);
```
##### Modify the referenced property
###### Change it to another document.
```typescript
const userDoc: FirestoreDocument = db.users.document("user_id");
villageDoc.update({owner: userDoc});
```
###### Set it to null
Make sure you define it nullable in Village DocumentData like this `owner: FirestoreDocument | null;`before set it to null:
```typescript
export class Village extends DocumentData {
@RefFDocument("users")
owner: FirestoreDocument | null;
}
```
```typescript
villageDoc.update({owner: null});
```
---
#### 4.2 Define a document property reference to array of other Documents
Continue our story: a village entity cannot only have owner :) It needs members which is an array of `User`.
You can use `@RefFDUnionArray` decorator the same as `@RefFDocument`. The only difference is `members` is an array of `FirestoreDocument` and is stored as an array of `DocumentReference` in firestore.
```
@RefFDUnionArray("users")
members: FDUnionArray>;
```
So, the completed Village class is:
```typescript
export class Village extends DocumentData {
@RefFDocument("users")
owner: FirestoreDocument;
@RefFDUnionArray("users")
members: FDUnionArray>;
constructor(public name: string, public description: string, owner: FirestoreDocument) {
super();
this.owner = owner;
this.members = new FDUnionArray(owner);
}
}
```
##### CRUD referenced document array:
```typescript
const villageDoc: FirestoreDocument = db.villages.document("village_id");
const village: Village = await villageDoc.get(); // you must get() parent document before reading referenced document array
const members: FDUnionArray> = village!.members;
```
###### Get a document from array:
```typescript
const village: Village = await villageDoc.get(); // you must get() parent document before reading referenced document array
const member0: FirestoreDocument = await village!.members[0];
const member0Data: Village = await member0.get();
console.log(member0Data.name, member0Data.avatarUrl);
```
###### Get all array
```typescript
const village: Village = await villageDoc.get(); // you must get() parent document before reading referenced document array
const villages: Village[] = await village.members.getAll();
```
###### push a element
```typescript
const userDoc: FirestoreDocument = db.users.document("user_id");
await village.members.pushDB(userDoc);
```
###### pop the array
```typescript
const popDoc = await village.members.popDB();
```
###### Splice the array
```typescript
await village.members.spliceDB(0, 1);
```
###### Update the whole array
```typescript
const village1Doc: FirestoreDocument = db.villages.document("village_1_id");
const village2Doc: FirestoreDocument = db.villages.document("village_2_id");
await village.update({members: [village1Doc, village2Doc]});
```
You may want to explore (or not) `FDArrayTracker` that can be get via `FirestoreDocument.linkedArray(propertyName)`.
### 5. Realtime support
**WARNING: This is experiment feature.**
While realtime features of firestore costs your firestore read/write quota a lot, it's recommend to avoid using it. Realtime Database is a good choice too.
#### 5.1 Realtime Collection
Use `@RealtimeCollection` decorator instead of `@Collection` decorator to define a realtime collection. Realtime Collection document's data is kept in sync with firestore in realtime.
You can add listeners to listen these changes too.
```typescript
import {OnCollectionChangedListener, RealtimeFirestoreDocument} from "@phamngocduy98/flashstore-admin";
const listener = new OnCollectionChangedListener();
listener.onDocumentAdded = (doc: RealtimeFirestoreDocument) => {
console.log(doc.value());
};
listener.onDocumentModified = (doc: RealtimeFirestoreDocument) => {
console.log(doc.value());
};
listener.onDocumentRemoved = (doc: RealtimeFirestoreDocument) => {
console.log(doc.value());
};
db.anyRealtimeCollection.addOnCollectionChangedListener(listener);
```
#### 5.2 Realtime Document
`RealtimeFirestoreDocument` is created be `RealtimeFirestoreCollection`. Its data is always up to date with the server in realtime. It support listener too.`
```typescript
import {OnValueChangedListener, RealtimeFirestoreDocument} from "@phamngocduy98/flashstore-admin";
const anyRealtimeDocument: RealtimeFirestoreDocument = db.anyRealtimeCollection.document("document_id");
const listener = new OnValueChangedListener();
listener.onValueChanged = (doc: RealtimeFirestoreDocument) => {
console.log(doc.value());
};
listener.onDocumentRemoved = (doc: RealtimeFirestoreDocument) => {
console.log(doc.value());
};
anyRealtimeDocument.addOnValueChangedListener(listener);
```
##### Realtime Document with Referenced Document Array:
###### Listen for changes
You can attach listener to listen when a item is being added to or removed from array
```typescript
const listener = OnArrayChangedListener();
listener.onItemsInserted = (docs: FirestoreDocument[]) => {
console.log(docs);
};
listener.onItemsRemoved = (docs: FirestoreDocument[]) => {
console.log(docs);
};
villageDoc.linkedArray("member").addOnArrayChangedListener(listener);
```
### 6. Batch support
```typescript
let batch = db.batch(); // or batch = anyDocument.root.batch();
userDoc.updateInBatch(batch, {name: "new name"});
userDoc.updateInBatch(batch, {avatarUrl: "new avatar"});
await batch.commit();
```
### 7. Access parent and root:
In each document or collection, you can get its parent or root but I do not recommend you do so.
```typescript
let root: MyDatabase = userDoc.root;
let userCollection: FirestoreCollection = userDoc.parentCollection;
```
### 8. A subcollection pattern:
**Reference to a sub-collection is NOT SUPPORTED in the current version of flashstore.
Please wait for updates!**
Pattern introduction:
- You want to allow clients to directly connect to firestore database.
- For example, you allow clients to create their own document to a sub-collection. Then you want reset the sub-collection for new writes, so you need to delete each document in the sub-collection.
- These delete operations cost a lot while after deletions, your sub-collection will be soon full of new documents.
=> So there is a solution, you can create a referenced document array to take care of which document is new. Now you can access all new documents without reset the sub-collection.