This tutorial is out of date and no longer maintained.
GraphQL and React work well together to provide developers with tools to streamline common web programming tasks, including real-time integrations. In this article, you’ll use React and GraphQL to make a real-time chat app.
GraphQL is a query spec built around the Graph algorithm developed by Facebook. Rather than just sending JSON payloads through REST to a server with the server then querying a database, GraphQL serves these queries right from the client. This way you end up with just what the client needs and not what the REST endpoint exposed. You’ll use the Apollo—a GraphQL client—to interact with you GraphQL server from your React frontend.
The process of setting up a server 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.
Warning: Graphcool will be sunset on July 1, 2020.
In this tutorial, you will:
To complete this tutorial, you will need:
This tutorial assumes knowledge of JavaScript and some familiarity with React and GraphQL.
create-react-app
has been the developers’ favorite tool for scaffolding React apps. Scaffold a new project in React by running the following command:
- npx create-react-app graphql-chat
This will 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
We will learn about what each of these dependencies does 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" />
These are references to CDN hosted files for Normalize and Skeleton. Normalize allows us to have a consistent baseline across different browser default styles. Skeleton provides us with tools for layouts and typography.
Rather than have a local server running, we can use an existing free hosted service to set up a GraphQL instance. This is not only free but less tasking and easier to get started with. Graphcool is useful for small, large, and growing projects.
To set up an instance, you need first to 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 set up 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 its 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 set up 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 will print an important URL you need to interact with your Graphcool server. Store this URL where you can refer to it.
Once you have the app deployed, open the playground to test if the deploy process has all your type definitions 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 will ask you which of the commands you want to run.
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. We’ll use 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 set up 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. We’ll use the split
method to tell the server when to use which link:
// ...
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 more difficult 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')
);
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 set up 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 from 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:
graphql
and gql
. These libraries will help set up and parse the query, respectively.gql
method using template tagging.graphql
HOC (Higher-Order Component) 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 (Document Object Model) 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>
);
}
}
// ...
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
.
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 C, U, D (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 helpers
import { graphql, compose } from 'react-apollo';
// ...
class App extends 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:
// ...
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} />
))}
{/* 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:
// ...
class App extends Component {
// ...
_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 real-time updates.
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 set up a subscription:
// ...
class App extends Component {
// ...
_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 real-time 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 values to update the old value.
You can kick off this subscription in the componentDidMount
lifecycle method:
// ...
class App extends Component {
// ...
componentDidMount() {
const from = window.prompt('username');
from && this.setState({ from });
this._subscribeToNewChats();
}
// ...
}
// ...
Once you run again, you’ll have a working chat app.
- npm start
You have now created a chat application using React and Graphcool.
In this tutorial, you built a real-time chat application with React and GraphQL. For more React content and tutorials, you can visit our React Topic page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!