Skip to content

Instantly share code, notes, and snippets.

@odykyi
Created January 5, 2018 16:14
Show Gist options
  • Save odykyi/af48c3ebd1c3f1f2d089df0bc2fb548e to your computer and use it in GitHub Desktop.
Save odykyi/af48c3ebd1c3f1f2d089df0bc2fb548e to your computer and use it in GitHub Desktop.
GraphQL vs Firebase.MD

GraphQL vs Firebase

With the variety of server-side technologies today, developers have a lot of choices when it comes to deciding what kind of backend to use for their next application.

In this article, we want to explore the differences between GraphQL and Firebase, two very popular server-side technologies.

Overview

Before diving into technical details, let's create some perspective on the two technologies and where they're coming from.

GraphQL is an API design paradigm that was open-sourced by Facebook in 2015, but has been in internal use much longer. Facebook released GraphQL as a specification, meaning that developers who want to use GraphQL either have to build a GraphQL server themselves or take advantage of services like Graphcool that deliver a GraphQL API out of the box.

Firebase was started as a Backend-as-a-Service focussing mainly on providing realtime functionality. After it was acquired by Google in 2014, it evolved into a multifunctional mobile and web platform also integrating services like analytics, hosting and crash reporting.

In that sense, GraphQL and Firebase are already very different. Where GraphQL only specifies a way how clients can consume an API, but it's irrevelevant where and how that API is provided, Firebase is a solution that ties any client closely to its platform. This vendor-lock has the potential to burn your fingers if Google decides to take down Firebase as Facebook did with Parse.

Structuring Data

GraphQL has its own type system that serves to describe the data that is going to be queried. The whole data model is expressed using the GraphQL Interface Definition Language (IDL) that speficy the custom types of the GraphQL API. A simple example of a schema for a Twitter application written in the IDL could look as follows:

type User {
  id: ID!
  username: String!  
  followers: [User] @relation(name: "Followers")
  tweets: [Tweet] @relation(name: "Tweets")
}

type Tweet {
  id: ID!
  text: String!
  author: User @relation(name: "Tweets")
}

Firebase on the other hand stores data in a less structured manner using a large JSON tree. The lack of typing with Firebase results in an overhead for the developer. They need to keep the stored data consistent by writing custom validation and error-handling logic so that only properly structured data makes it into the database. As is stated in the Firebase documentation, "building a properly structured database requires quite a bit of forethought". When designing the structure of the data, it is recommended to follow best practices and trying to keep the data tree as flat as possible to avoid performance traps.

Reading Data

GraphQL and Firebase vary greatly in the way how clients can read data from the backend. GraphQL uses the concept of queries. A query is a declarative specification of the data that should be retrieved from the database. It follows the structure of the types that have been defined in the schema. Let's consider a simple example for the data model of the Twitter app from the previous section:

{
  allUsers {
    username
    tweets(last: 10) {
      text
      author {
        username
      }
    }
  }
}

This query asks for all users in the database. The server response will return the username of each user and their last 10 tweets where each tweet carries the tweet's text and the username of its author. Another nicety of GraphQL is that the structure of the response precisely matches the structure of the query. This eliminates uncertainties about the actual contents of JSON payloads when querying anAPI. Another way of thinking of a GraphQL query is simply a JSON object without values, but when resolved actually does carry the values that are specified.

Firebase exposes a REST API that can be used to retrieve information from the backend. A much more common approach however is to use a Firebase SDK for the platform you're developing on and interact with the DB using its API. With the SDK, you'll have a local reference to the database that allows you to subscribe to any changes and get realtime updates. It's also possible to retrieve specific data without subscribing but requesting them only once. A query in Firebase is described by the path into the JSON tree. So, for example to retrieve the tweets of a certain user, the following query could be sent (using the Firebase Web SDK as an example):

var usersTweetsRef = firebase.database().ref('users/' + userId + '/tweets')
usersTweetsRef.once('value', snapshot => {
  console.log(snapshot.val()) // print the contents of the snapshot
})

It should also be noted that GraphQL allows for much more complex queries than Firebase. As we saw, with Firebase, it is only possible to query into specific paths of the JSON tree, but e.g. any filtering or ordering would have to happen on the client. GraphQL allows for much more sophisticated query functionality with advanced filtering mechanisms, so that a lot of work can be offloaded to the server.

Updating Data

In GraphQL, changes to the backend are done using so-called mutations. Mutations syntactically follow the structure of queries, but they're also causing writes to the database rather than only reading from it.

A mutation for the sample Twitter data model from before could look as follows:

mutation {
  createTweet(text: "GraphQL is awesome!", authorId: "abcdefghijklmnopq") {
    id
  }
}

This mutation creates a new tweet, associating it with the user that has id abcdefghijklmnopq while also returning the id of the new tweet. Mutations, just like queries, allow to specify data requirements that should be returned by the server after the mutation was performed. This allows for easily accessing the new data without additional server roundtrips!

With Firebase, the most common scenario to make changes to the database is again by using its SDK (though the REST API also offers this functionality). The SDK exposes four different methods to save data: set, update, push and transaction, each of them coming with specific characteristics that you should be aware of to avoid unintented side-effects. Creating a tweet using Firebase could look somewhat similar to this:

var tweetsRef = firebase.database().ref('/tweets')
tweetsRef.set({
  TODO
})

Realtime Functionality

Firebase's initial product was a realtime database which eventually evolved into the more sophisticated backend solution that it is today. Because of these roots, realtime functionality is baked deep into its core. The most common way of consuming the realtime functionality of Firebase is by subscribing to specific data in the tree and getting notified when it changes. Taking again the Web SDK as an example, we'd use the on method on the local DB reference and then pass a callback that gets executed on every change:

var usersTweetsRef = firebase.database().ref('users/' + userId + '/tweets')
usersTweetsRef.on('value', snapshot => {
  console.log(snapshot.val()) // print the contents of the snapshot
})

What kind of data is requested again is determined by the path into the JSON tree. So, in the example above we're subscribing to new tweets that are created by the user with id userId.

Realtime updates in GraphQL can be implemented using so-called subscriptions. Subscriptions are syntactically similar to queries and mutations, thus again allowing the developer to specify their data requirements in a declarative fashion. The data will then arrive in the application in shape that was specified in the subscription:

subscription {
  allTweets(filter: {
    authorId: "abcdefghijklmnopq"
  }) {
    id
    text
  }
}

This subsription will be fired every time the author with id abcdefghijklmnopq creates a new tweet. Any client that listens for changes with this subscription, will receive the id and the text of the new tweet. If you want to know more about how subscriptions work in GraphQL and how they can be used in a React application, you can check out this tutorial.

Access Permissions

With Firebase, the only way to declare access rights for specific data points is by using the Bolt Compiler which is currently in beta and has not yet become a standard in the Firebase ecosystem. Bolt uses a custom syntax to specify security rules that express which users should be allowed to read and write certain paths in the JSON tree.

With GraphQL, dealing with permissions really depends on the server-side implementation and there is no out-of-the-box solution. Graphcool uses a sophisticated permission system that is based on JSON Web Tokens (JWT). It allows to specify custom rules that clearly state under which conditions a certain user should be able to read, create, update or delete any data.

Client Technologies

A major part of the value of any server-side technology is the ease of using it on the client.

As already mentioned, Firebase requires usage of their custom SDK to interface with the backend - the REST API is only supposed to be a fallback and covers edge cases when some functionality can't be implemented only using the SDK. Firebase provides SDKs for all major development platforms such as Android, iOS and the Web. For the latter, there are also specific bindings for UI frameworks like React or Angular. However, when using Firebase, the developers make themselves completely dependent on the infrastructure that is provided by Google. If Google takes down Firebase, the application's whole data layer will have to be rewritten!

In principle, GraphQL could be consumed using plain HTTP, simply sending the queries and mutations using standard POST requests. However, usage of client-side frameworks that implement common functionality help to save a lot of time and give developers a head-start. With GraphQL, there are three major client libraries at the moment:

  • Lokka: Lightweight client that essentially serves as a thin wrapper on top of HTTP and implements basic support for queries, mutations and caching.
  • Relay: Facebook's GraphQL client that was opensource alongside GraphQL. Relay is a very advanced client that comes with a notable learning curve but greatly benefits long-term developer productivy and especially shines in the context of complex and large-scale application. Relay can only be used with React or React Native.
  • Apollo: Apollo sits somewhere between Lokka and Relay on the complexity-scale. It offers lots handy features such as caching, optmistic UI, pagination, server-side rendering and prefetching of data. As the only GraphQL client at the moment, Apollo also supports subscriptions. It also integrates with all major UI frameworks such as React, Angular or Vue and is also available on iOS and Android.

For a deep-down comparison of Relay and Apollo, you can check out this article as well as the Learn Apollo and Learn Relay guides for comprehensive tutorials.

Summary

Firebase is developed around a realtime database that drives the design of the whole platform. Its main use cases are applications with a simple data model, such as messenging services or lightweight games.

For larger-scale projects, Firebase falls mostly short due to lack of complex queries, a sophisticated permission system and cloesly coupling any client to the platform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment