Build a realtime Instagram clone — Part 2: Connecting the UI with GraphQL data
You should have completed part one of the series.
This is part 2 of a 4 part tutorial. You can find part 1 here, part 3 here and part 4 here.
In the last part, of this series, we set up the UI for our React Instagram clone. If you haven’t already, you can read up on it here. In this part, we are going to take a look at how to connect the UI to GraphQL Data
Prerequisites
- Should have read the first part of the series
- Basic knowledge of JavaScript
- Node installed on your machine
- NPM installed on your machine
Set up the server
To allow us to serve data to the UI, let’s create an API server for the application. The server will be built using NodeJS and GraphQL.
What is GraphQL
GraphQL allows you to describe how to ask for data. With GraphQL, the client specifies the exact resources it needs instead of making requests to REST Endpoints that return everything. The GraphQL API has three main building blocks which are queries, schema, and resolvers.
To get started, create a server
directory in the project root of the react-instagram-clone
app. Here’s a link to the Github repository for more reference.
mkdir server
Initialize a new Node project and install the following modules:
cd server
npm init
npm install --save express graphql express-graphql cors
Create a server.js
file in the server/
directory:
touch server.js
Now, edit the server.js
file as follows. First, import the necessary node modules:
// server/server.js
let express = require("express");
let graphqlHTTP = require("express-graphql");
let { buildSchema } = require("graphql");
let cors = require("cors");
[...]
Next thing is to construct a schema. In GraphQL, schemas are constructed using GraphQL Schema Language.
// server/server.js
[...]
let schema = buildSchema(`
type User {
id : String!
nickname : String!
avatar : String!
}
type Post {
id: String!
user: User!
caption : String!
image : String!
}
type Query{
user(id: String) : User!
post(user_id: String, post_id: String) : Post!
posts(user_id: String) : [Post]
}
`);
[...]
In specifying the schema, above, we also added a query**.** Query fields also allow you to add arguments which we will need in this case to display specific data.
Next thing to do is to add some stock data for the application. Add the following to your server.js
file:
// server/server.js
[...]
// Maps id to User object
let userslist = {
a: {
id: "a",
nickname: "Chris",
avatar: "https://www.laravelnigeria.com/img/chris.jpg"
},
[...]
};
let postslist = {
a: {
a: {
id: "a",
user: userslist["a"],
caption: "Moving the community!",
image: "https://pbs.twimg.com/media/DOXI0IEXkAAkokm.jpg"
},
b: {
id: "b",
user: userslist["a"],
caption: "Angular Book :)",
image:
"https://cdn-images-1.medium.com/max/1000/1*ltLfTw87lE-Dqt-BKNdj1A.jpeg"
},
c: {
id: "c",
user: userslist["a"],
caption: "Me at Frontstack.io",
image: "https://pbs.twimg.com/media/DNNhrp6W0AAbk7Y.jpg:large"
},
d: {
id: "d",
user: userslist["a"],
caption: "Moving the community!",
image: "https://pbs.twimg.com/media/DOXI0IEXkAAkokm.jpg"
}
}
};
[...]
The data is truncated for brevity. You can fetch the complete data from the server.js
file on Github.
Now that this is specified, the next thing to do is to specify the resolver function for the API. The resolver ****tells your server how to handle an incoming query telling it where to get the data for a given field. Add the resolver to the server.js
file that looks like this:
// server/server.js
[...]
// The root provides a resolver function for each API endpoint
let root = {
user: function({ id }) {
return userslist[id];
},
post: function({ user_id , post_id }) {
return postslist[user_id][post_id];
},
posts: function({ user_id }){
return Object.values(postslist[user_id]);
}
};
[...]
Here the functions user
and post
return data that matches the criteria specified above. posts
returns all the posts created by a specific user. Now that this is all done, let’s create an Express app and have the server listen for incoming requests:
// server/server.js
[...]
let app = express();
app.use(cors());
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
})
);
// set application port
app.listen(4000);
GraphQL also comes with an interactive console that could be displayed to allow developers get familiar with GraphQL queries. The grapgiql: true
option specifies that this should also be mounted on the /graphql
route of the express server.
Now, when you run the server.js
file, your server will be hosted at http://localhost:4000/graphql
.
node server
When you visit the URL in the browser, you get the following:
You can test it out by pasting this query into the GraphiQL console:
{
user(id: "a"){
nickname,
}
}
When you hit the run button, you can get the result:
This is the query in the above screenshot, with one modification:
{
user(id: "a"){
nickname,
avatar
}
}
As you can see in the result below, all that needs to be changed is the description of what you want from the server.
From the screenshots above, you can see that the server only returns the data that was requested of it. Nothing more, nothing less.
Connect the React client to the GraphQL server
Now, let’s see how to connect the server to our React UI that was created in the part of this series. To do this, we are going to make use of Apollo.
Getting set up with Apollo
First, let’s install the necessary packages in the root folder of the react project.
cd react-instagram-clone
npm install apollo-boost react-apollo graphql-tag graphql --save
Now that the installations are complete, create a new Apollo Client in the src/app.js
file:
// src/app.js
[...]
import ApolloClient from 'apollo-boost';
const client = new ApolloClient({
uri : "http://localhost:4000"
})
[...]
The uri
used above is that of the GraphQL server that was created earlier.
Connecting the Apollo Client to React
Then wrap the content of the App component in an ApolloProvider
passing the created client as one of the props.
// src/app.js
[...]
import { ApolloProvider } from "react-apollo";
const client = new ApolloClient({
uri: "http://localhost:4000/graphql"
});
const App = () => {
return (
<ApolloProvider client={client}>
<div className="App">
<Header />
<section className="App-main">
<Post />
</section>
</div>
</ApolloProvider>
);
};
export default App;
This allows us to use the created client all through the different components of our application without having to create a new Apollo Client every time.
Render server-provisioned data
Now, let’s begin to make queries to render posts from the GraphQL server created earlier on. To do this, you’ll have to tweak the src/components/Post/index.js
as follows:
Import the necessary modules that would be needed to construct queries:
// src/components/Post/index.js
import { Query } from "react-apollo";
import gql from "graphql-tag";
[...]
Now, make query inside the Post
component like this:
// src/components/Post/index.js
const Post = () => {
return (
<Query
query={gql`
{
post(user_id: "a", post_id: "a") {
image
caption
user {
nickname
avatar
}
}
}
`}
>
// handle result of the query
[...]
</Query>
)
}
We are going to replace the dots with the query handler but first let’s understand how query itself. The code above creates a GraphQL query that will be executed. When this query is run using the GraphiQL UI this result is obtained.
You can paste this query in your GraphiQL:
{
post(user_id: "a", post_id: "a") {
image
caption
user {
nickname
avatar
}
}
}
When you run it, you get the result below:
Now, let’s handle the result as follows:
// src/components/Post/index.js
[...]
{({ loading, error, data }) => {
if (loading) return <p>Loading Post...</p>;
if (error) return <p>Error loading Post:(</p>;
let image = data.post.image;
let caption = data.post.caption;
let user = data.post.user;
// return JSX to be rendered
[...]
Checks are made to see if the query is executed successfully. If it is, the data returned from the query is obtained and the result is handled as follows:
// src/components/Post/index.js
[...]
return (
<article className="Post" ref="Post">
<header>
<div className="Post-user">
<div className="Post-user-avatar">
<img src={user.avatar} alt={user.nickname} />
</div>
<div className="Post-user-nickname">
<span>{user.nickname}</span>
</div>
</div>
</header>
<div className="Post-image">
<div className="Post-image-bg">
<img alt={caption} src={image} />
</div>
</div>
<div className="Post-caption">
<strong>{user.nickname}</strong> {caption}
</div>
</article>
);
}}
</Query>
);
};
export default Post;
Now, when you run the react app using the command:
yarn start # or npm start -- based on the package manager of choice
the application is loaded on http://localhost:3000/
and the following view is obtained:
Make sure that the GraphQL server is still running
Fetching and displaying multiple posts
In a real life scenario, you don’t expect to only have one post showing on your version of Instagram . What needs to be done now is that a new Posts
component needs to be created to allow for dynamic queries and display of multiple posts. Let’s see how to do this.
Create a new Posts
folder in the /src/components
directory
mkdir Posts && cd Posts
Create an index.js
file in the Posts
folder:
touch index.js
Let’s edit the Posts/index.js
file to look like this:
First, import the necessary node modules:
// src/components/Posts/index.js
import React from "react";
import "./Posts.css";
import { Query } from "react-apollo";
import gql from "graphql-tag";
import Post from "../Post";
[...]
Then make Query for all the posts in the Posts
component:
// src/components/Posts/index.js
[...]
const Posts = () => {
return (
<Query
query={gql`
{
posts(user_id: "a"){
id
user{
nickname
avatar
}
image
caption
}
}
`}
>
[...]
Now, let’s handle the results of the Query like we did earlier on:
// src/components/Posts/index.js
[...]
{({loading, error, data}) => {
if (loading) return <p>Loading Posts...</p>;
if (error) return <p>Error Fetching Posts...</p>;
let posts = data.posts;
return <div className="Posts">
{posts.map(post => <Post nickname={post.user.nickname} avatar={post.user.avatar} image={post.image} caption={post.caption} key={post.id}/>)}
</div>;
}}
</Query>
);
}
export default Posts;
Notice how the Post
component was used above. Let’s tweak the Post
component to allow for this. Edit the src/components/Post/index.js
to look as follows:
import React, { Component } from "react";
import "./Post.css";
class Post extends Component {
render() {
const nickname = this.props.nickname;
const avatar = this.props.avatar;
const image = this.props.image;
const caption = this.props.caption;
return (
<article className="Post" ref="Post">
<header>
<div className="Post-user">
<div className="Post-user-avatar">
<img src={avatar} alt={nickname} />
</div>
<div className="Post-user-nickname">
<span>{nickname}</span>
</div>
</div>
</header>
<div className="Post-image">
<div className="Post-image-bg">
<img alt={caption} src={image} />
</div>
</div>
<div className="Post-caption">
<strong>{nickname}</strong> {caption}
</div>
</article>
);
}
}
export default Post;
As seen above, the Post component has the nickname
, avatar
, image
and caption
which are dynamically added for each post that is fetched from the GraphQL server.
Recall in the src/components/Posts/index.js
we did this:
// src/components/Posts/index.js
return
<div className="Posts">
{
posts.map(
post => <Post nickname={post.user.nickname} avatar={post.user.avatar} image={post.image} caption={post.caption} key={post.id}/>
)
}
</div>;
What happened here is that all the posts received from the GraphQL server were mapped to Post components with the necessary data properties.
Create a Posts.css
file in the src/components/Posts
directory that will contain any styling you wish to add to the Posts component:
touch Posts.css
Now, when you visit your application at http://localhost:3000/
you get the following:
Ensure that your GraphQL server is still running
Conclusion
In this part of the series, we took a look at GraphQL and how to create a GraphQL server. We also saw how to link our existing React UI to the GraphQL server using the Apollo Client. In the next part of the series, we are going to look at how to add realtime feed updates to the Instagram clone application using Pusher. Here’s a link to the full Github repository if you’re interested.
27 April 2018
by Christian Nwamba