In this codelab, you'll learn how to use the Firebase platform to easily create Web applications and you will implement and deploy a chat client using Firebase.

What you'll learn

What you'll need

Clone the GitHub repository from the command line:

git clone https://github.com/firebase/friendlychat

Import the starter app

Using your IDE open or import the πŸ“ web-start directory from the sample code directory. This directory contains the starting code for the codelab which consists of fully functional Chat Web App.

Create project

In the Firebase console click on CREATE NEW PROJECT and call it FriendlyChat.

Enable Google Auth

To let users sign-in on the web app we'll use Google auth which needs to be enabled.

In the Firebase Console open the Authentication section > SIGN IN METHOD tab (click here to go there) you need to enable the Google Sign-in Provider and click SAVE. This will allow users to sign-in the Web app with their Google accounts

The Firebase Command Line Interface (CLI) will allow you to serve your web apps locally and deploy your web app to Firebase hosting.

To install the CLI run the following npm command:

npm -g install firebase-tools

To verify that the CLI has been installed correctly open a console and run:

firebase version

Make sure the Firebase version is above 3.3.0

Authorize the Firebase CLI by running:

firebase login

Make sure you are in the web-start directory then set up the Firebase CLI to use your Firebase Project:

firebase use --add

Then select your Project ID and follow the instructions.

Now that you have imported and configured your project you are ready to run the app for the first time. Open a console at the web-start folder and run firebase serve:

firebase serve

This command should display this in the console:

Listening at http://localhost:5000

The web app should now be served from http://localhost:5000 open it. You should see your app's not (yet!) functioning UI:

The app cannot do anything right now but with your help it will soon! We only have laid out the UI for you so far. Let's now build a realtime chat!

Initialize Firebase Auth

The Firebase SDK should be ready to use since you have imported and initialized it in the index.html file. In this application we'll be using the Firebase Realtime Database, Cloud Storage for Firebase and Firebase Authentication. Modify the FriendlyChat.prototype.initFirebase function in the scripts/main.js file so that it sets some shortcuts to the Firebase SDK features and initiates auth:

main.js

// Sets up shortcuts to Firebase features and initiate firebase auth.
FriendlyChat.prototype.initFirebase = function() {
  // Shortcuts to Firebase SDK features.
  this.auth = firebase.auth();
  this.database = firebase.database();
  this.storage = firebase.storage();
  // Initiates Firebase auth and listen to auth state changes.
  this.auth.onAuthStateChanged(this.onAuthStateChanged.bind(this));
};

Authorize Firebase with Google

When the user clicks the Sign in with Google button the FriendlyChat.prototype.signIn function gets triggered (we already set that up for you!). At this point we want to authorize Firebase using Google as the Identity Provider. We'll sign in using a popup (Several other methods are available). Change the FriendlyChat.prototype.signIn function with:

main.js

// Signs-in Friendly Chat.
FriendlyChat.prototype.signIn = function() {
  // Sign in Firebase using popup auth and Google as the identity provider.
  var provider = new firebase.auth.GoogleAuthProvider();
  this.auth.signInWithPopup(provider);
};

The FriendlyChat.prototype.signOut function is triggered when the user clicks the Sign out button. Add the following line to make sure we sign out of Firebase:

main.js

// Signs-out of Friendly Chat.
FriendlyChat.prototype.signOut = function() {
  // Sign out of Firebase.
  this.auth.signOut();
};

We want to display the signed-in user's profile pic and name in the top bar. Earlier we've set up the FriendlyChat.prototype.onAuthStateChanged function to trigger when the auth state changes. This function gets passed a Firebase User object when triggered. Change the two lines with a TODO to read the user's profile pic and name:

main.js

// Triggers when the auth state change for instance when the user signs-in or signs-out.
FriendlyChat.prototype.onAuthStateChanged = function(user) {
  if (user) { // User is signed in!
    // Get profile pic and user's name from the Firebase user object.
    var profilePicUrl = user.photoURL; // Only change these two lines!
    var userName = user.displayName;   // Only change these two lines!

    ...

We display an error message if the users tries to send messages when the user is not signed-in. To detect if the user is actually signed-in add these few lines to the top of the FriendlyChat.prototype.checkSignedInWithMessage function where the TODO is located:

main.js

// Returns true if user is signed-in. Otherwise false and displays a message.
FriendlyChat.prototype.checkSignedInWithMessage = function() {
  // Return true if the user is signed in Firebase
  if (this.auth.currentUser) {
    return true;
  }

  ...

Test Signing-in to the app

  1. Reload your app if it is still being served or run firebase serve on the command line to start serving the app from http://localhost:5000 and open it in your browser.
  2. Sign-In using the Sign In button
  3. After Signing in the profile pic and name of the user should be displayed:

Import Messages

In your project in Firebase console visit the Database section on the left navigation bar. On this page you will see the data that is stored in your Firebase Realtime Database.

In the overflow menu of the Database select Import JSON. Browse to the initial_messages.json file at the root of the repository, select it then click the Import button. This will replace any data currently in your database.

You could also edit the database manually, using the green + and red x to add and remove items manually or use the Firebase CLI with this command:

firebase database:set / ../initial_messages.json

After importing the JSON file your database should contain the following elements:

friendlychat-12345/
    messages/
        -K2ib4H77rj0LYewF7dP/
            text: "Hello"
            name: "anonymous"
        -K2ib5JHRbbL0NrztUfO/
            text: "How are you"
            name: "anonymous"
        -K2ib62mjHh34CAUbide/
            text: "I am fine"
            name: "anonymous"

These are a few sample chat messages to get us started with reading from the Database.

Synchronize Messages

To synchronize messages on the app we'll need to add listeners that triggers when changes are made to the data and then create a UI element that show new messages.

Add code that listens to newly added messages to the app's UI. To do this modify the FriendlyChat.prototype.loadMessages function. This is where we'll register the listeners that listens to changes made to the data. We'll only display the last 12 messages of the chat to avoid displaying a very long history on load.

main.js

// Loads chat messages history and listens for upcoming ones.
FriendlyChat.prototype.loadMessages = function() {
  // Reference to the /messages/ database path.
  this.messagesRef = this.database.ref('messages');
  // Make sure we remove all previous listeners.
  this.messagesRef.off();

  // Loads the last 12 messages and listen for new ones.
  var setMessage = function(data) {
    var val = data.val();
    this.displayMessage(data.key, val.name, val.text, val.photoUrl, val.imageUrl);
  }.bind(this);
  this.messagesRef.limitToLast(12).on('child_added', setMessage);
  this.messagesRef.limitToLast(12).on('child_changed', setMessage);
};

Test Message Sync

  1. Reload your app if it is still being served or run firebase serve on the command line to start serving the app from http://localhost:5000 and open it in your browser.
  2. The sample messages we imported earlier into the database should be displayed in the Friendly-Chat UI (see below). You can also manually add new messages directly from the Database section of the Firebase console. Congratulations, you are reading real-time database entries in your app!

Implement Message Sending

In this section you will add the ability for users to send messages. The code snippet below is triggered upon clicks on the SEND button and pushes a message object with the contents of the message field to the Firebase database. The push() method adds an automatically generated key to the pushed object's path. These keys are sequential which ensures that the new messages will be added to the end of the list. Update the FriendlyChat.prototype.saveMessage function with:

main.js

// Saves a new message on the Firebase DB.
FriendlyChat.prototype.saveMessage = function(e) {
  e.preventDefault();
  // Check that the user entered a message and is signed in.
  if (this.messageInput.value && this.checkSignedInWithMessage()) {
    var currentUser = this.auth.currentUser;
    // Add a new message entry to the Firebase Database.
    this.messagesRef.push({
      name: currentUser.displayName,
      text: this.messageInput.value,
      photoUrl: currentUser.photoURL || '/images/profile_placeholder.png'
    }).then(function() {
      // Clear message text field and SEND button state.
      FriendlyChat.resetMaterialTextfield(this.messageInput);
      this.toggleButton();
    }.bind(this)).catch(function(error) {
      console.error('Error writing new message to Firebase Database', error);
    });
  }
};

Test Sending Messages

  1. Reload your app if it is still being served or run firebase serve on the command line to start serving the app from http://localhost:5000 and open it in your browser.
  2. After signing-in, enter a message and hit the send button, the new message should be visible in the app UI and in the Firebase console with your user photo and name:

We'll now add a feature that shares images by uploading them to Cloud Storage. Cloud Storage for Firebase is a file/blob storage service.

Save images to Cloud Storage

We have already added for you a button that triggers a file picker dialog. After selecting a file the FriendlyChat.prototype.saveImageMessage function is triggered and you can get a reference to the selected file. Now we'll add code that:

Add the following at the bottom of the FriendlyChat.prototype.saveImageMessage function where the TODO is located:

main.js

// Saves the a new message containing an image URI in Firebase.
// This first saves the image in Cloud Storage.
FriendlyChat.prototype.saveImageMessage = function(event) {

  ...

  // Check if the user is signed-in
  if (this.checkSignedInWithMessage()) {

    // We add a message with a loading icon that will get updated with the shared image.
    var currentUser = this.auth.currentUser;
    this.messagesRef.push({
      name: currentUser.displayName,
      imageUrl: FriendlyChat.LOADING_IMAGE_URL,
      photoUrl: currentUser.photoURL || '/images/profile_placeholder.png'
    }).then(function(data) {

      // Upload the image to Cloud Storage.
      var filePath = currentUser.uid + '/' + data.key + '/' + file.name;
      return this.storage.ref(filePath).put(file).then(function(snapshot) {

        // Get the file's Storage URI and update the chat message placeholder.
        var fullPath = snapshot.metadata.fullPath;
        return data.update({imageUrl: this.storage.ref(fullPath).toString()});
      }.bind(this));
    }.bind(this)).catch(function(error) {
      console.error('There was an error uploading a file to Cloud Storage:', error);
    });
  }
};

Display images from Cloud Storage

In the chat messages we saved the Cloud Storage reference of the images. These are of the form gs://<bucket>/<uid>/<postId>/<file_name>. To display these images we need to query Cloud Storage for a URL.

To do this replace the FriendlyChat.prototype.setImageUrl function content with:

main.js

// Sets the URL of the given img element with the URL of the image stored in Cloud Storage.
FriendlyChat.prototype.setImageUrl = function(imageUri, imgElement) {
  // If the image is a Cloud Storage URI we fetch the URL.
  if (imageUri.startsWith('gs://')) {
    imgElement.src = FriendlyChat.LOADING_IMAGE_URL; // Display a loading image first.
    this.storage.refFromURL(imageUri).getMetadata().then(function(metadata) {
      imgElement.src = metadata.downloadURLs[0];
    });
  } else {
    imgElement.src = imageUri;
  }
};

Test Sending images

  1. Reload your app if it is still being served or run firebase serve on the command line to start serving the app from http://localhost:5000 then open it in your browser.
  2. After signing-in, click the image upload button: and select an image file using the file picker, a new message should be visible in the app UI with your selected image:
  3. If you try adding an image while not signed-in you should see a Toast telling you that you must sign in in order to add images.

We'll now add support for browser notifications. This way your users could receive a notification when a new message has been posted in the chat. Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably deliver messages and notifications at no cost.

Whitelist the GCM Sender ID

In the web app manifest you need to specify the gcm_sender_id, a hard-coded value indicating that FCM is authorized to send messages to this app. Friendlychat already has a manifest.json configuration file so add the browser sender ID exactly as shown (do not change the value):

manifest.json

{
  "name": "Friendly Chat",
  "short_name": "Friendly Chat",
  "start_url": "/index.html",
  "display": "standalone",
  "orientation": "portrait",
  "gcm_sender_id": "103953800507"
}

Add the Firebase Messaging service worker

The web app needs a Service Worker that will receive and display web notifications. Create a file named firebase-messaging-sw.js at the root of your project with the following content:

firebase-messaging-sw.js

importScripts('/__/firebase/3.8.0/firebase-app.js');
importScripts('/__/firebase/3.8.0/firebase-messaging.js');
importScripts('/__/firebase/init.js');

firebase.messaging();

The service worker simply loads and initializes the Firebase Cloud Messaging SDK which will take care of displaying notifications.

Get FCM device tokens

When notifications have been enabled on a device or browser, you'll be given a device token. This device token is what we use to send a notification to a particular device.

When the user signs-in we call the FriendlyChat.prototype.saveMessagingDeviceToken function. That's where we'll get the FCM device token and save it to the Realtime Database.

Update the FriendlyChat.prototype.saveMessagingDeviceToken function with:

main.js

// Saves the messaging device token to the datastore.
FriendlyChat.prototype.saveMessagingDeviceToken = function() {
  firebase.messaging().getToken().then(function(currentToken) {
    if (currentToken) {
      console.log('Got FCM device token:', currentToken);
      // Saving the Device Token to the datastore.
      firebase.database().ref('/fcmTokens').child(currentToken)
          .set(firebase.auth().currentUser.uid);
    } else {
      // Need to request permissions to show notifications.
      this.requestNotificationsPermissions();
    }
  }.bind(this)).catch(function(error){
    console.error('Unable to get messaging token.', error);
  });
};

However this will not work initially. For your app to be able to retrieve the device token the user needs to grant your app permission to show notifications.

Request permissions to show notifications

When the user has not yet granted you permission to show notifications you will not be given a device token. In this case we call the firebase.messaging().requestPermission() method which will display a browser dialog asking for this permission:

Update the FriendlyChat.prototype.requestNotificationsPermissions function with:

main.js

// Requests permissions to show notifications.
FriendlyChat.prototype.requestNotificationsPermissions = function() {
  console.log('Requesting notifications permission...');
  firebase.messaging().requestPermission().then(function() {
    // Notification permission granted.
    this.saveMessagingDeviceToken();
  }.bind(this)).catch(function(error) {
    console.error('Unable to get permission to notify.', error);
  });
};

Get your Device token

  1. Reload your app if it is still being served or run firebase serve on the command line to start serving the app from http://localhost:5000 then open it in your browser.
  2. After signing-in, you should see the Notifications permission dialog being displayed:
  3. Click Allow and open the JavaScript console of your browser, you should see a message that reads:
    Got FCM device token: cWL6w:APA91bHP...4jDPL_A-wPP06GJp1OuekTaTZI5K2Tu
  4. Copy your device token, you will need it for the next step.

Send a notification to your device

Now that you have your device token you can send a notification. You will also need your Firebase app's Server Key. to get it open your app's Firebase Console > Project Settings > Cloud Messaging and copy the Server Key.

To send a notification you need to send the following HTTP request:

POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key=YOUR_SERVER_KEY

{
  "notification": {
    "title": "New chat message!",
    "body": "There is a new message in FriendlyChat",
    "icon": "/images/profile_placeholder.png",
    "click_action": "http://localhost:5000"
  },
  "to":"YOUR_DEVICE_TOKEN"
}

You can do this by using cURL in a command line:

curl -H "Content-Type: application/json" \
     -H "Authorization: key=YOUR_SERVER_KEY" \
     -d '{
           "notification": {
             "title": "New chat message!",
             "body": "There is a new message in FriendlyChat",
             "icon": "/images/profile_placeholder.png",
             "click_action": "http://localhost:5000"
           },
           "to": "YOUR_DEVICE_TOKEN"
         }' \
     https://fcm.googleapis.com/fcm/send

The notification will only appear if your FriendlyChat app is in the background. You must, for instance, navigate away or display another tab for the notification to be displayed.

When the app is in the foreground, you can catch the messages sent by FCM.

If your app is on the background you should see a notification appear such as:

Set Database Security Rules

The Firebase Realtime Database uses a specific rules language to define access rights, security and data validations.

New Firebase Projects are set up with default rules that only allow authenticated users to use the Realtime Database. You can view and modify these rules in the Database section of Firebase console under the RULES tab. You should be seeing the default rules:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

This rule allows any signed-in user to read and write any data in your Firebase Relatime database.

Update the rules to the following which only allows users to read and write their own Firebase Cloud Messaging device tokens:

database-rules.json

{
  "rules": {
    "messages": {
      ".read": "auth !== null",
      ".write": "auth !== null"
    },
    "fcmTokens": {
      "$token": {
        // Users can only read their own device tokens
        ".read": "data.val() === auth.uid",
        // Users can only write to their own device tokens
        ".write": "!data.exists() || data.val() === auth.uid",
        // value has to be the UID of the user
        ".validate": "newData.val() === auth.uid"
      }
    }
  }
}

You can update these rules manually directly in the Firebase console. Alternatively you can write these rules in a file and apply them to your project using the CLI:

firebase.json

{
  "database": {
    "rules": "database-rules.json"
  },
  "hosting": {
    "public": "./",
    "ignore": [
      "firebase.json",
      "database-rules.json",
      "storage.rules"
    ]
  }
}

Deploy Database Security Rules

You can then deploy these rules with the Firebase CLI using firebase deploy --only database:

firebase deploy --only database

This is the console output you should see:

=== Deploying to 'friendlychat-12345'...

i  deploying database
βœ”  database: rules ready to deploy.
i  starting release process (may take several minutes)...

βœ”  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-12345/overview

Set Storage Security Rules

Cloud Storage for Firebase uses a specific rules language to define access rights, security and data validations.

New Firebase projects are set up with default rules that only allow authenticated users to use Cloud Storage. You can view and modifies these rules in the Storage section of Firebase console under the RULES tab. You should be seeing the following default rule:

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

This rule allow any signed-in user to read and write any files in your Storage bucket.

Update the rules to the following which only allows users to write to their own folder:

storage.rules

service firebase.storage {
  match /b/{bucket}/o {
    match /{userId}/{timeStamp}/{fileName} {
      allow write: if request.auth.uid == userId;
      allow read;
    }
  }
}

You can update these rules manually directly in the Firebase console. Alternatively you can write these rules in a file and apply them to your project using the CLI:

firebase.json

{
  // If you went through the "Realtime Database Security Rules" step.
  "database": {
    "rules": "database-rules.json"
  },
  "storage": {
    "rules": "storage.rules"
  },
  "hosting": {
    "public": "./",
    "ignore": [
      "firebase.json",
      "database-rules.json",
      "storage.rules"
    ]
  }
}

Deploy Storage Security Rules

You can then deploy these rules with the Firebase CLI using firebase deploy --only storage:

firebase deploy --only storage

This is the console output you should see:

=== Deploying to 'friendlychat-12345'...

i  deploying storage
i  storage: checking rules for compilation errors...
βœ”  storage: rules file compiled successfully
i  starting release process (may take several minutes)...

βœ”  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-12345/overview

Firebase comes with a hosting service that will serve your static assets/web app. You deploy your files to Firebase Hosting using the Firebase CLI. Before deploying you need to specify which files will be deployed in your firebase.json file. We have already done this for you because this was required to serve the file for development through this codelab. These settings are specified under the hosting attribute:

firebase.json

{
  // If you went through the "Realtime Database Security Rules" step.
  "database": {
    "rules": "database-rules.json"
  },
  // If you went through the "Storage Security Rules" step.
  "storage": {
    "rules": "storage.rules"
  },
  "hosting": {
    "public": "./",
    "ignore": [
      "firebase.json",
      "database-rules.json",
      "storage.rules"
    ]
  }
}

This will tell the CLI that we want to deploy all files in the current directory ( "public": "./" ) with the exception of the files listed in the ignore array.

Now deploy your files to Firebase static hosting by running firebase deploy:

firebase deploy

This is the console output you should see:

=== Deploying to 'friendlychat-12345'...

i  deploying database, storage, hosting
βœ”  database: rules ready to deploy.
i  storage: checking rules for compilation errors...
βœ”  storage: rules file compiled successfully
i  hosting: preparing ./ directory for upload...
βœ”  hosting: ./ folder uploaded successfully
βœ”  hosting: 9 files uploaded successfully
i  starting release process (may take several minutes)...

βœ”  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-12345/overview
Hosting URL: https://friendlychat-12345.firebaseapp.com

Then simply visit your web app hosted on Firebase Hosting on https://<project-id>.firebaseapp.com or by running firebase open hosting:site.

The Hosting tab in the Firebase console will display useful information such as the history of your deploys with the ability to rollback to previous versions and the ability to set up a custom domain.

You have used Firebase to easily build a real-time chat application.

What we've covered

Next Steps

Learn More