How to Build a Realtime Chat App with React and GraphQL

Chris Nwamba
👁️ 4,470 views
💬 comments

GraphQL and React are a match made in heaven. Their ecosystem provides a lot of tools to simplify common web programming tasks including realtime integrations. In this article, you're going to learn how to use only React and GraphQL to make a realtime apps (precisely a chat app).

What You Will Learn

  • How to setup a React + GraphQL project
  • Using Apollo to interact with a GraphQL server from React
  • Querying and mutating entities
  • Realtime messaging and subscription

I'm assuming you know the basic concepts of GraphQL and React. Therefore, this article will focus on showing you how to build a realtime app with both tools while skipping explanations for basic concepts.

GraphQL, Apollo, Graphcool, Duh...

Yes I understand these terms can be daunting. They were not so easy for me to get along with as well but with practice and knowing the fundamental knowledge, you will start getting a hang of it.

GraphQL

Basically, GraphQL is a query spec that is built around the Graph algorithm. It's developed by Facebook and it's so powerful that it seems to be replacing the REST we love. Rather than just sending JSON payloads through REST to a server, then the server in-turn queries a database, GraphQL gives you the power to serve these queries right from the client. This way you end up with just what the client needs and not what the REST endpoint exposed.

Apollo

GraphQL is just a spec that you can follow to build your own implementation. But that's a hard journey. This is why tools like Apollo were built to serve as GraphQL clients. From a beginner perspective, you can see it as an SDK for interacting with a GraphQL server that follows a GraphQL spec.

Graphcool

Speaking of servers, the process of setting up one and the tooling involved can also get overwhelming. Sometimes you might even get it wrong, hence exposing your products to security vulnerabilities. Graphcool is a highly extensive hosted GraphQL server that you can work with. You can manipulate anything on the server using its serverless functions. The service has a free tier that is generous enough to get you into business without paying a dime.

Setting Up a React Project

create-react-app has been developers favorite tool for scaffolding React apps. We're just going to stick to it because there is a high chance you know how it works and probably used it before. Scaffold a new project in React by running the following command:

 create-react-app graphql-chat

This would create a new folder named graphql-chat and download all the necessary React files you need to work with.

Update the dependencies in the package.json as follows:

"dependencies": {

  "apollo-client-preset": "^1.0.6",
  "apollo-link-ws": "^1.0.4",
  "graphql": "^0.12.3",
  "graphql-tag": "^2.6.1",
  "react": "^16.2.0",
  "react-apollo": "^2.0.4",
  "react-dom": "^16.2.0",
  "react-scripts": "1.0.17",
  "subscriptions-transport-ws": "^0.9.4"
},

Then run the install command to download all of these files:

npm install
# OR
yarn

We will learn about what each of these dependencies do when we encounter them.

Some global CSS files needs to be included so as to relieve styling concerns from us for this project. Update the head tag in public/index.html to include the following CSS files:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" />

Setting up a GraphQL Instance

Rather than have a local server running, we can use an existing free hosted service to setup a GraphQL instance. This is not only free but less tasking and easier to get started with. Graphcool is fantastic for small projects and also amazing for larger projects when your app grows.

To setup an instance, you need to first install the Graphcool CLI tool. This tool exposes commands for creating new GraphQL servers, updating them, as well as deploying them. It can also be used to manage the Graphcool cloud functions. Run the following command to install:

npm install -g graphcool-framework

Navigate to the React app we just setup through the terminal and start a new Graphool server:

graphcool-framework init server

This command will create a folder in the React project named server. The server command contains type and schema definitions for your GraphQL data. You can refer to Understanding GraphQL API Queries as well as the articles in this search result to learn more about GraphQL fundamentals.

For our chat app, we just need to focus on the types.graphql file generated in the server folder. This is where you tell GraphQl what the structure of your data looks like. It's known as a type definition. Replace it's content with the following:

type Chat @model {
  id: ID! @isUnique
  from: String!
  content: String!
  createdAt: DateTime!
}

You need to deploy this schema to the Graphcool server:

graphcool-framework deploy

This will first open a browser for you to setup a Graphcool account and then deploy your instance. You can open the instance from the menu on the top left of your Graphcool dashboard:

Back in the terminal, the process prints some important URL you need to interact with your Graphcool server. Please store this URL where you can refer to them:

Once you have the app deployed, open the playground to test if the deploy process has all your type definition in place:

graphcool-framework playground

Run the following query and mutation in the editor on the left and click the execute button to execute them:

    query FETCH_CHATS{
      allChats{
        from,
        content
      }
    }

    mutation CREATE_CHAT {
      createChat(content: "test", from: "test") {
        content,
        from
      }
    }

The playground would ask you which of the commands you want to run:

Provide GraphQL Instance to React

A React project is ready, and a GraphQL server is cooked. What next? We need to tie them together with all those modules we installed with the package.json file.

Let's start with importing them. Update the src/index.js entry file to import the following dependencies:

    import { ApolloProvider } from 'react-apollo';
    import { ApolloClient } from 'apollo-client';
    import { HttpLink } from 'apollo-link-http';
    import { InMemoryCache } from 'apollo-cache-inmemory';
    import { ApolloLink, split } from 'apollo-client-preset'
    import { WebSocketLink } from 'apollo-link-ws'
    import { getMainDefinition } from 'apollo-utilities'

Right under the imports, configure the WebSocket link. You can do this using the WebSocketLink module:

const wsLink = new WebSocketLink({

  uri: '[Subscriptions API URI]',
  options: {
    reconnect: true
  }
})

The constructor function takes an object with config options. The uri is required and should be the same as the Subscriptions API URI you received after deploying. The options are optional, but we are using the reconnect option to ask WebSocket to try reconnecting after a failed attempt.

We are not just making a WebSocket connection. We also need to setup an HTTP connection for request-response operations. Add this right below the WebSocket link setup:

const httpLink = new HttpLink({ uri: '[SIMPLE API URI]' })

The same as the WebSocketLink constructor but uses the Simple API URI. We don't need to pass in any configuration option.

At this point, we have two links. How do we tell the server when to use which. This is achievable using the split method we imported above:

const link = split(

  ({ query }) => {
    const { kind, operation } = getMainDefinition(query)
    return kind === 'OperationDefinition' && operation === 'subscription'
  },
  wsLink,
  httpLink,
)

The split method takes three arguments. The first is a test that returns a boolean. If the boolean value is true, the request is forwarded to the second (wsLink) argument. If false, it's forwarded to the third (httpLink) argument.

Now we can create an Apollo client with the returned link:

const client = new ApolloClient({

  link,
  cache: new InMemoryCache()
})

You can make requests directly to your server using the URLs provided. This is a bit messier than using a wrapper library that provides functionalities to simplify server interaction. Apollo is one of such libraries.

Provide the client using the AppProvider component:

ReactDOM.render(

  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

Querying the GraphQL Server

With the tie between our React app and our GraphQL set, it is time to start querying the database and displaying the data in the browser.

Update the src/App.js to setup a query:

import React, { Component } from 'react';

// Import GraphQL helpers
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

// App component styles
import './App.css';

class App extends Component {
  state = {
    from: 'anonymous',
    content: ''
  };
  componentDidMount() {
    // Get username form prompt
    // when page loads
    const from = window.prompt('username');
    from && this.setState({ from });
  }
  render() {
    // Coming up next
  }
}

const ALL_CHATS_QUERY = gql`
  query AllChatsQuery {
    allChats {
      id
      createdAt
      from
      content
    }
  }
`;

export default graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' })(App);

Let's breakdown what is going on here:

  • We first import graphql and gql. This libraries will help setup and parse the query respectively.
  • At the end of the component class definition, we create the query. It looks exactly like what we did in the playground but wrapped with the gql method using template tagging.
  • The graphql HOC is then used to expose the result of this query to the App component's props.

You can now render the query in the DOM through the props:

 // Chatbox UI component
import Chatbox from './components/Chatbox';

    class App extends Component {

      //...

      render() {
        const allChats = this.props.allChatsQuery.allChats || [];
        return (
          <div className="">
            <div className="container">
              <h2>Chats</h2>
              {allChats.map(message => (
                <Chatbox key={message.id} message={message} />
              ))}
            </div>
          </div>
        );
      }
    }

    // ...

    export default graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' })(App);

The render method iterates over each of the query results and prints them to the screen using the Chatbox component. This is what the component looks like in components/Chatbox.js:

import React from 'react';
import './Chatbox.css'
    const Chatbox = ({message}) => (
      <div className="chat-box">
        <div className="chat-message">
          <h5>{message.from}</h5>
          <p>
            {message.content}
          </p>
        </div>
      </div>
    );
    export default Chatbox;

You can refer to the repo to get the basic styles for Chatbox.css and App.css. Here is what the current output should look like when you run:

Remember I added some data via the playground which is why I have some messages in the view.

Creating New Messages

Mutations in GraphQL are used to create, edit, and delete values from your database. Where a query is the R (Read) in CRUD, a mutation can be the CUD (Create, Update, Delete). We are already able to read existing messages in our chat app. Next thing we should worry about is creating those messages from our React app.

Just like the way we created a query, we can also create a mutation:

import { graphql, compose } from 'react-apollo';

    // App component ...

    const CREATE_CHAT_MUTATION = gql`
      mutation CreateChatMutation($content: String!, $from: String!) {
        createChat(content: $content, from: $from) {
          id
          createdAt
          from
          content
        }
      }
    `;

    export default compose(
      graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' }),
      graphql(CREATE_CHAT_MUTATION, { name: 'createChatMutation' })
    )(App);

The mutation is a lot like a query but receives parameters -- content and from. We need to wrap the App component with both this mutation and our existing query. For this reason, we also import the compose method and use this method to wrap both HOCs.

In the render method, we can have an input element that collects these message contents:

render() {
   const allChats = this.props.allChatsQuery.allChats || [];
       return (
          <div className="">
            <div className="container">
              <h2>Chats</h2>
              {allChats.map(message => (
                <Chatbox key={message.id} message={message} />
              ))}

              {/* Message content input */}
              <input
                value={this.state.content}
                onChange={e => this.setState({ content: e.target.value })}
                type="text"
                placeholder="Start typing"
                onKeyPress={this._createChat}
              />
            </div>
          </div>
        );
      }
    }

The input triggers an event at every keyPress, and this event calls the _createChat method. Here is the definition of this method in the App class:

_createChat = async e => {
     if (e.key === 'Enter') {
       const { content, from } = this.state;
        await this.props.createChatMutation({
          variables: { content, from }
        });
        this.setState({ content: '' });
      }
    };

We only want to run the mutation when the enter key is pressed. Notice how the createChatMutation is also exposed on the props and the variable is passed in as an object to the mutation method.

When you send a new message, nothing happens until you reload:

This calls for realtime updates.

Setting up Realtime Subscriptions

Subscriptions in GraphQL are used to listen for changes by connected clients. These clients can act on these changes accordingly. Probably by updating the user interface with the changed data or even sending push notifications. The underlying technology that powers subscriptions is the well known WebSockets.

Add this method to the App class to setup a subscription:

_subscribeToNewChats = () => {
      this.props.allChatsQuery.subscribeToMore({
          document: gql`
            subscription {
              Chat(filter: { mutation_in: [CREATED] }) {
                node {
                  id
                  from
                  content
                  createdAt
                }
              }
            }
          `,
          updateQuery: (previous, { subscriptionData }) => {
            const newChatLinks = [
              ...previous.allChats,
              subscriptionData.data.Chat.node
            ];
            const result = {
              ...previous,
              allChats: newChatLinks
            };
            return result;
          }
        });
      };

The allChatsQuery exposes a subscribeToMore method. Once this method is called, it opens up a channel for realtime communication. The method takes an object which we can define the query document and an updateQuery method.

The document defines a subscription and listens for when a mutation occurs on the Chat entity before triggering an event. The update method receives the old and new value, and we are using these new value to update the old value.

You can kick off this subscription in the componentDidMount lifecycle method:

this._subscribeToNewChats();

Now run once more, and you should have a running chat app:

Further Learning

You already learned a lot, but there is more to GraphQL. You can dig our Scotch articles on GraphQL to discover more. The How to GraphQL tutorials are also excellent and walks you through steps on learning about the GraphQL fundamentals. Feel free to hit the comments section if you run into issues or find a bug.

Chris Nwamba

91 posts

JavaScript Preacher. Building the web with the JS community.