Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Associate Firebase Users to Database Records

Episode 19 written by Jeff Delaney
full courses and content on fireship.io

In this lesson, we are going to associate database records with users who authenticated via Firebase. Code originally produced in the realtime database lesson will be refactored maintain a relationships with Firebase users. You don’t need to go through that lesson, but it wouldn’t hurt. First, let’s talk about how data relationships are managed in a NoSQL database.

Thinking in NoSQL Terms

If you come from a SQL background, you’re used to modeling data around the concept of relationships. In NoSQL, you don’t model data based on relationships, but rather on queries you plan to run against it.

In NoSQL, you should always ask “What is the most efficient way to query this data?”, because operations must be executed quickly. Usually, that means designing a database that is shallow or that avoids large nested documents. You will probably need to duplicate data and that’s OK - I realize that might freak you out if you have a SQL background. Consider this fat and wide database design:

Usually a Bad Idea in NoSQL

-|users
-|userId
-|posts
-|posdId
-|comments
-|commentId
-|likes

Now imagine you wanted to loop over the users just to display their usernames. You would also need load their posts, the post comments, and the likes – all that data just for some usernames. We can do better with a tall and skinny design.

Refactoring to a Shallow Design

-|users
-|userId

-|posts
-|userId
-|postId

-|comments
-|postId

-|likes
-|commentId

Not only does this offer better performance on individual queries, it keeps the logic in distinct areas. Now that we understand that concept, let’s create some actual data relations with Angular 4 and Firebase.

1. When Data is Queried based on the User

When you have data that you plan on querying based the authenticated user, such as a user’s twitter feed, you can nest data under the Firebase auth.uid.

-|items
-|userId
-|itemId

The  data is nested under each Firebase uid The relationship is created by nesting items under the user ID

The Items Service

In order to get the items associated we need to obtain the current user’s ID from Firebase.

In the service, we subscribe to the AngularFire2 AuthState, which will emit the user’s uid. Now we can query the database to get items that belong to the user because they are nested under this unique ID. Notice this.userId is added to the db.list call.

import { Injectable } from '@angular/core';
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';


export class Item {
body: string;
}


@Injectable()
export class ItemService {

items: FirebaseListObservable<Item[]> = null;
userId: string;

constructor(private db: AngularFireDatabase, private afAuth: AngularFireAuth) {
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
})
}


// Return an observable list with optional query
// You will usually call this from OnInit in a component
getItemsList(): FirebaseListObservable<Item[]> {
if (!this.userId) return;
this.items = this.db.list(`items/${this.userId}`);
return this.items
}


createItem(item: Item) {
this.items.push(item)
}

}

Backend Security with Firebase Rules

It is important to secure data on the backend. In this example, we only want the current user to have read/write access to their data. We can do this by matching a wildcard variable $uid to the auth.uid.

"rules": {
"items": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}

2. When Data is Queried based on the Item

Sometimes data has a user association, but does not get queried by the user, such as a comment feed, votes, etc. In this case, you can set the association on each item.

-|items
-|itemId
-|user: uid

User Id attribute relationship tracking

Updated Service

This user relationship is tracked by giving the Item class a userId attribute. When a new Item is created, the attribute is updated with the current user’s uid. All other service code remain the same, with the following minor changes:

export class Item {
body: string;
userId: string;
}

getItemsList(): FirebaseListObservable<Item[]> {
if (!this.userId) return;
this.items = this.db.list(`items/`);
return this.items
}


// Create a brand new item
createItem(item: Item) {
item.userId = this.userId
this.items.push(item)
}

Firebase Rules

This rule grants write access if the the data’s userId matches the auth.uid OR it isnull, which would be expected when creating a new item.

"$itemId": {
".write": "auth.uid === data.child('userId').val()
|| data.child('userId').val() === null"
}

3. Data that Belongs to Multiple Users

Some data can be associated with multiple users, such as a collaborative project or chat room. In that case, you can set the uid as the $key with a value of true.

-|items
-|itemId
-|members
-|$uid: true

Membership style relationship

Membership Style Service

In this scenario, users can join() or leave() an item. We will grab an object observable from the database based on the item’s push $key, then update or remove the current user’s uid in the members section.

join(itemKey) {
const data = { [this.userId]: true}
const members = this.db.object(`items/${itemKey}/members`)
members.update(data)
}

leave(itemKey) {
const member = this.db.object(`items/${itemKey}/members/${this.userId}`)
member.remove()
}

Firebase Rules

In this scenario, backend rules can be similar to method two based on the user that created the item. Alternatively, you could assign certain certain members at moderator attribute. Check out the Firebase Database Examples Lesson for more advanced backend rule ideas.

That’s it for user data relationships with Firebase and Angular.