🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Migrating from Firebase to Pusher Chatkit

  • Wern Ancheta
August 1st, 2019
You need basic knowledge of JavaScript and Firebase.

In this tutorial, I’ll be showing you how you can migrate your existing Firebase chat code to Chatkit. Along the way, I’ll highlight some of the key differences between Firebase and Chatkit when it comes to implementing chat.

Many developers start out with Firebase when building realtime apps. But as their needs get more specific, they realize that Firebase is really just a realtime database. You can use it as the foundation for most realtime apps, but sooner or later it’s going to hold you back when implementing specific features such as chat.

Prerequisites

Basic knowledge of JavaScript and Firebase is required to follow this tutorial.

Migrating the code

Now we’re ready to start looking at some Firebase code and their Chatkit equivalent.

Initializing the connection

In Firebase, here’s how you initialize the connection:

    // Firebase
    import firebase from 'firebase';

    if (!firebase.apps.length) { 
      firebase.initializeApp({
        apiKey: "YOUR FIREBASE API KEY",
        authDomain: "{YOUR_FIREBASE_AUTH_DOMAIN}.firebaseapp.com",
        databaseURL: "{YOUR_FIREBASE_DATABASE_URL}.firebaseio.com",
        projectId: "YOUR FIREBASE PROJECT ID",
        appId: "YOUR FIREBASE APP ID"
      });
    }

In Chatkit, there’s no need to initialize the connection to the server. This is because it’s only done when you authenticate the user.

Authenticating the user

In Firebase, you authenticate the user with the following:

    // Firebase
    firebase.auth()
      .signInWithEmailAndPassword("YOUR USER'S EMAIL", "YOUR USER'S PASSWORD")
      .then((res) => {
        // successfully authenticated user
      }, (err) => {
        console.log("error: ", err);
      });

Firebase also has a way to authenticate user’s anonymously, social login, and phone numbers. Chatkit doesn’t have those features, but you can easily integrate services like Auth0 to add authentication features to your app.

In Chatkit, you can authenticate the user by using the ChatManager:

    // Chatkit
    const chatManager = new ChatManager({
      instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID,
      userId: "YOUR USER'S UNIQUE ID",
      tokenProvider: new TokenProvider({ url: 'YOUR TOKEN PROVIDER URL' }) // can be your test token provider for now
    });

    const currentUser = await chatManager.connect();

Here’s a break down of the options above:

  • instanceLocator - this is the equivalent of your Firebase app’s app ID. It’s used by Chatkit to uniquely identify your app.
  • userID - the unique user ID of the user who is currently using the app. You can create users via the console tab in your Chatkit app instance’s dashboard. Note that these users have to be real users of your app. As you have learned earlier, Chatkit doesn’t have it’s own email and password, or social login features. So you have to use something like Auth0 to provide this to your app. When a user signs up, you have to create a Chatkit user using your server. The id that you specified will be the value you need to pass to the userId option when you use the ChatManager.
  • tokenProvider - the token provider is a server which authenticates the user. Its main responsibility is to determine whether the current user is really a user of the app (someone can just pass in an arbitrary userId which matches with a real user of your app). The example below uses an Express server to authenticate the user:
    const Chatkit = require("@pusher/chatkit-server");
    const chatkit = new Chatkit.default({
      instanceLocator: "YOUR INSTANCE_LOCATOR_ID",
      key: "YOUR CHATKIT_SECRET"
    });

    app.post("/auth", (req, res) => {
      const { user_id } = req.query;
      // todo: check if user exists on your database

      if (user_exists) {
        const authData = chatkit.authenticate({
          userId: user_id
        });

        res.status(authData.status)
           .send(authData.body);
      }
    });

When you’re just getting started, you can actually use Chatkit’s test token provider to authenticate your users. To enable it, go to your Chatkit app instance’s settings page, then check the ENABLED? checkbox under the Credentials tab.

Listing rooms

In most chat apps, you usually have multiple rooms where users can chat. These rooms can either be public or private. Here’s how you would usually get the list of rooms in Firebase:

    // Firebase
    firebase.database().ref().child('messages').on('value', (val) => {
       const rooms = Object.keys(val);
      // append rooms to the UI
    });

The code above assumes your schema looks like this (where 2A-yoyo-room and general are the rooms, and each one contains an array of messages sent on that room):

In Chatkit, you can get the list of rooms that the current user is a member of from the rooms property. While joinable rooms can be fetched using the getJoinableRooms() method:

    // Chatkit
    const user_rooms = currentUser.rooms; // rooms that the current user has joined
    const rooms = await currentUser.getJoinableRooms(); // public rooms that the current user haven't joined yet

Just like users, rooms can also be created via the console:

What about private rooms though? At the time of writing this tutorial, Chatkit doesn’t have functionality for requesting membership or joining a private room yet. This is probably the reason why it doesn’t have a method for getting the list of all rooms from the client side. If you want that, you can use axios (or any HTTP client) to make a request to your server:

    // Chatkit
    try {
      const response = await axios.post(
        `${CHAT_SERVER_BASE_URL}/rooms`, 
        { user_id: this.user_id }
      );
      const { rooms } = response.data;
      // todo: append list of rooms to UI
    } catch (get_rooms_err) {
      console.log("error getting rooms: ", get_rooms_err);
    }

You can then make a request to get the list of all rooms (including private ones) from your server:

    // Chatkit server
    app.post("/rooms", async (req, res) => {
      const { user_id } = req.body;
      try {
        const user_rooms = await chatkit.getUserRooms({
          userId: user_id
        });
        const user_room_ids = user_rooms.map(room => room.id);

        const all_rooms = await chatkit.getRooms({
          includePrivate: true
        });

        const rooms = all_rooms.map((room) => {
          if (user_room_ids.indexOf(room.id) !== -1) {
            room.joined = true;
          }
          return room;
        });

        res.send({ rooms });
      } catch (get_rooms_err) {
        console.log("error getting rooms: ", get_rooms_err);
      }
    });

From the server, you can actually add users directly to a private room. But I don’t really recommend it because rooms are usually private for a reason. You can use this method only if you can implement some sort of approval system:

    chatkit.addUsersToRoom({
      roomId: "YOUR ROOM ID",
      userIds: ["YOUR USER ID"]
    });

Receiving messages

Firebase, being a realtime database, it’s main strength is being able to subscribe to any data in your database. So you can subscribe to any change that is made to any of its properties. The example below subscribes to new messages being added to a specific room:

    // Firebase
    const room_id = "YOUR ROOM ID";
    firebase.database().ref().child(`messages/{room_id}`).limitToLast(20)
      .on('child_added', (snapshot) => {
         // todo: extract return value of snapshot.val()
      });

In Chatkit, you can subscribe to updates in the room using the subscribeToRoomMultipart() method:

    // Chatkit
    currentUser.subscribeToRoomMultipart({
      roomId: "YOUR ROOM ID",
      hooks: {
        onMessage: ({ id, sender, parts, createdAt }) => {
          const text = parts.find(part => part.partType === 'inline').payload.content;
          // todo: append message
        }
      }
    });

In the code above, we’re subscribing to the onMessage event. This gets triggered any time a new message is sent in the room. Aside from that, you can also subscribe to the following:

  • onUserStartedTyping and onUserStartedTyping - for implementing typing indicators you usually see on chat apps.
  • onUserJoined - when a new user joins the room.
  • onUserLeft - when a room member leaves the room.
  • onPresenceChanged - when a room member either goes offline or online.
  • onNewReadCursor - when a new read cursor is added. This is useful for listening for changes in another member’s read cursor (the latest message they’ve read).

All of these are also possible with Firebase but you’ll have to exert an extra effort in implementing it. In Chatkit, all you have to do is subscribe to those events and update the UI accordingly.

Sending messages

In Firebase, you can send messages using the push() method. Firebase doesn’t have a concept of which user performed a specific operation so you also have to get the reference to the current user and include it as an additional property to your message:

    // Firebase
    const user = firebase.auth().currentUser;
    const room_id = "YOUR ROOM ID";
    const message = {
      text: "hey!",
      user,
      timestamp: firebase.database.ServerValue.TIMESTAMP,
    }
    firebase.database().ref().child(`messages/${room_id}`).push(message);

In Chatkit, you can send messages using the sendMultipartMessage() method:

    // Chatkit
    let message_parts = [
      { type: "text/plain", content: "YOUR MESSAGE" }
    ];

    await this.currentUser.sendMultipartMessage({
      roomId: "YOUR ROOM ID",
      parts: message_parts
    });

The example above shows only how to send the “text” part of the message. But Chatkit also allows you to attach files and URL’s (can point out to any type of file that’s publicly available on a server).

Getting previous messages

In Firebase, you can’t really load previous messages without shedding some blood. This is because there’s no way of fetching data in descending order. The closest thing you have is by specifying a field to order by and then set its range:

    // Firebase
    firebase.database().ref().child(`messages/${room_id}`)
      .orderByChild('number')
      .startAt(start)
      .endAt(end)
      .on('child_added', (snapshot) => {
        // todo: prepend the message to the current message list
      });

The only problem with the above approach is that you have to increment the value of the field you’re ordering by (number) by yourself every time a new message gets sent. But that’s really hard to implement because there can be more than one people in the room, and they can send messages at the same time. This leads to a duplication for the value of number which then ruins the results that you get from the above query.

In Chatkit, things are far simpler. You can get older messages by using the fetchMultipartMessages() method. Just supply the ID of the earliest message that’s currently loaded in your app and pass older for the direction option. Note that this will first return the message that came before the initialId you specified, then the one that came before that, and so on so you’ll have to reverse the result before prepending it to your currently loaded messages:

    // Chatkit
    const earliest_message_id = Math.min(
      ...messages.map(m => parseInt(m._id))
    );

    let earlier_messages = await currentUser.fetchMultipartMessages({
      roomId: "YOUR ROOM ID",
      initialId: earliest_message_id,
      direction: "older", // fetch messages older than the supplied ID (passing "newer" does the opposite)
      limit: 10 // how many messages to fetch
    });

    // todo: prepend earlier_messages to your existing messages

Conclusion

In this tutorial, you learned how to migrate your existing Firebase codebase to use Chatkit. As you have seen, Firebase makes it hard to implement chat because it’s not really made for it. Firebase is just a general-purpose realtime database while Chatkit specializes in chat.

If you’re interested, I’ve put together a GitHub repo which uses some of the code shown in this tutorial to create a React Native chat app on both Firebase and Chatkit.

Clone the project repository
  • Chat
  • JavaScript
  • React Native
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.