Across our last three articles in this series, we explored many concepts of GraphQL and Dgraph. As we continue to do so, today we’re going to integrate Apollo into the app and build more of the UI. We’ll also modify the GraphQL schema to improve our data model, resembling more of a real-world social media app. This will also allow us to generate the application feed containing posts from users one is following.
Let’s look at the list of topics:
If you want, you can take a look at the previous articles in this series that follow up to this one:
You can find all the code changes for today’s article here.
We can improve our data model by changing the follower
and following
fields of our User
type. Technically, these should be actual User
entities rather than mere integers.
type User {
...
following: [User] @hasInverse(field: follower)
follower: [User] @hasInverse(field: following)
}
The accounts each user is following
, and their follwer
s, are under the hood User
types. Here we’ve established that relationship by using the hasInverse
directive. Now it would be possible to get details on the accounts we’re following by querying for the following
fields. You’ll soon see that in action when rendering the feed.
After making the edits, click Deploy to re-deploy your backend.
Apollo is a GraphQL client for JavaScript. You can use it with frameworks/libraries like React, Angular, etc. This is what your React app will use under the hood to make GraphQL requests.
Generally, all of the resources of a GraphQL service are exposed over HTTP via a single endpoint. A client like Apollo comes packed with many features that make a GraphQL service scalable over HTTP. Some of these functionalities include caching, UI integration, etc.
So let’s install Apollo:
npm install @apollo/client graphql
This will install the following packages:
@apollo/client
: This is the core of Apollo client. It has tools for caching, error handling, etc.graphql
: You need this package for parsing GraphQL queries.In your .env
file, create another variable holding your Dgraph Cloud endpoint.
REACT_APP_BACKEND_ENDPOINT="<<your-Dgraph-Cloud-endpoint>>"
You can access it from your Dgraph Cloud dashboard.
We’ll create a new component holding everything related to Apollo.
ApolloWrapper.js
in the Components
directory. Then import the following definitions there:import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import App from '../App';
ApolloWrapper
, create a new ApolloClient
instance using the ApolloClient
constructor:const ApolloWrapper = () => {
const client = new ApolloClient({
uri: process.env.REACT_APP_BACKEND_ENDPOINT,
cache: new InMemoryCache(),
});
};
What’s happening here?
uri
parameter is the address of the backend endpoint, that we pull in from the .env
file.InMemoryCache
. This caches query results so that the app can access data faster.After setting up the client, you need to connect it with your application. That’s where the ApolloProvider
component comes in. It will wrap the root of your React app, exposing the client throughout the component tree. This way, any component down the tree can use GraphQL if necessary.
You can do that in the ApolloWrapper
component:
const ApolloWrapper = () => {
...
return (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
};
export default ApolloWrapper;
What’s happening here?
client
as a prop to ApolloProvider
.App
component inside the ApolloWrapper.js
file. This sits at the highest level of the component tree; wrapping this will expose the client to lower levels. So we wrap App
inside ApolloProvider
and return the whole thing.In index.js
file, we just need to place the ApolloWrapper
component inside the ReactDOM.render
function. Below is how your index.js
file should look like now:
import React from "react";
import ReactDOM from "react-dom";
import { Auth0Provider } from "@auth0/auth0-react";
import ApolloWrapper from './Components/ApolloWrapper';
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
ReactDOM.render(
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={window.location.origin}
>
<React.StrictMode>
<ApolloWrapper />
</React.StrictMode>
</Auth0Provider>,
document.getElementById("root")
);
Run npm start
from your terminal and everything should work like before.
That’s it. This is how simple it can be to integrate a client with a React application. But we need to make some changes for it to work completely with our requirements.
Follow me to the next section.
Remember that the HTTP requests need to have the X-Auth-Token
header containing the id_token
so that Dgraph can verify the requests. We’ve been doing that manually from the Dgraph Cloud console. You need to configure Apollo so that it automatically sends this header with the requests.
ApolloWrapper.js
, make the following imports:import { setContext } from "@apollo/client/link/context";
import { useAuth0 } from "@auth0/auth0-react";
import { useState, useEffect } from "react";
ApolloWrapper
component, put the following bit of code:const { isAuthenticated, getIdTokenClaims } = useAuth0();
const [xAuthToken, setXAuthToken] = useState("");
useEffect(() => {
const getToken = async () => {
const token = isAuthenticated ? await getIdTokenClaims() : "";
setXAuthToken(token);
};
getToken();
}, [getIdTokenClaims, isAuthenticated]);
What’s happening here?
useAuth0
hook, we extract two things:
isAuthenticated
, indicating the authentication status of a user.getIdTokenClaims
that yields the id_token
.useEffect
hook to obtain the id_token
asynchronously, by calling the getIdTokenClaims
function. We only call this function if isAuthenticated
is true, i.e. the user has logged in. Then we set the state with the value of that token that now resides in the xAuthToken
variable. The hook only runs when either getIdTokenClaims
or isAuthenticated
changes.At this point, I should point out that what we’re doing here is changing how data flows between our backend and Apollo client. The client now needs to send some extra information to the API, so it’s not just plain HTTP requests anymore without any authentication flow.
Apollo has a library called Apollo Link that lets us modify the usual data flow. We can write multiple “links”, that each does a separate thing, and then chain them together.
So let’s write a link whose only purpose is to take care of HTTP headers. Specifically, it’ll set the X-Auth-Token
header, and return any other header that might exist. We’re also going to write a link that just handles the HTTP request. Later we’ll chain these two together inside the ApolloClient
instance.
import {
...
createHttpLink,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
HttpLink
using the createHttpLink
function, supplying your backend URL as the uri
parameter like before:const httpLink = createHttpLink({
uri: process.env.REACT_APP_BACKEND_ENDPOINT,
});
And now the link for setting authentication headers:
const authLink = setContext((_, { headers, ...rest }) => {
if (!xAuthToken) return { headers, ...rest };
return {
...rest,
headers: {
...headers,
"X-Auth-Token": xAuthToken.__raw,
},
};
});
What’s happening here?
setContext
function from Apollo to take care of the headers.
rest
). They’re both inside an object.X-Auth-Token
header, setting its value equal to the id_token
field of the state variable.Note: If you’re curious, you can examine the object getIdTokenClaims
returns. Just do a console.log(xAuthToken)
inside the getToken
function of the useEffect
hook, right after you set the state.
Now you can create the ApolloClient
instance like this:
const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
With that, we’ve finished integrating Apollo with our application! We can now start making GraphQL queries from within the app. For starters, let’s generate the application feed containing posts from users someone is following.
We’ll start fresh with a little bit of data that we can use to render the feed.
First, go to your Dgraph Cloud dashboard and open the Schema window.
Click on Drop Data at the bottom.
On the modal that appears, select Drop All Data.
You’ll see a modal again where you need to confirm it. After confirming, you’ll see a Success message stating a successful removal of all the data in your Dgraph backend.
Now we’ll start adding some test data for trying out Apollo and sending GraphQL requests.
As a first step, let’s simulate opening an account for ourselves.
First, we need to log in from the UI and obtain the ID token from Auth0. Then as usual we’ll use it as a request header. With how we’ve set up the authentication-authorization cycle, Dgraph will always expect a verified JWT in the X-Auth-Token
HTTP header.
We’ve gone through the following steps before. You can access them at the Getting the JWT token section of the authentication article, but here’s a recap:
npm start
from the terminal.id_token
field.id_token
as the value of an X-Auth-Token
header.Now we can dispatch an addUser
mutation.
mutation AddAUser($userInput: [AddUserInput!]!) {
addUser(input:$userInput) {
user {
name
username
}
}
}
The variable will hold the necessary details. For example, here’s mine:
{
"userInput": [
{
"username": "sakib",
"name": "Abu Sakib",
"email": "[email protected]",
"about": "Programming, writing and literature.",
"avatarImageURL": "https://robohash.org/sakib"
}
]
}
Next, we’ll add another account using a different email address. It’d be good to use an email address that you can log into yourself. There are corner cases that by design have come into play so that you can follow along with the article by yourself easily:
sakib
will contain posts from this second user felix
.So run the same mutation with the following data:
{
"userInput": [
{
"username": "felix",
"name": "Felix Biedermann",
"email": "[email protected]",
"about": "I tell stories.",
"avatarImageURL": "https://robohash.org/felix"
}
]
}
As the next step, let’s simulate the event of user sakib
following the user felix
. We can make use of the following updateUser
mutation for doing so:
mutation FollowAnAccount($updateInput: UpdateUserInput!) {
updateUser(input:$updateInput) {
user {
username
email
following {
username
email
about
}
}
}
}
The variable data will be:
{
"updateInput": {
"filter": {
"email": { "eq": "[email protected]" }
},
"set": {
"following": [
{
"username": "felix"
}
]
}
}
}
Great! You’re now following a user on the app.
Next, let’s simulate the event of felix
posting some images on his InstaClone account.
felix
) from the UI of your app.id_token
and paste it as a Request Header like before.mutation AddPosts($postData: [AddPostInput!]!) {
addPost(input:$postData) {
post {
description
id
likes
description
}
}
}
With the variable data as:
{
"postData": [
{
"imageURL": "https://picsum.photos/id/156/2177/3264",
"description": "We went to the beach!",
"postedBy": {
"username": "felix"
},
"likes": 0
},
{
"imageURL": "https://picsum.photos/id/154/3264/2176",
"description": "I don't feel like going back from this trip...",
"postedBy": {
"username": "felix"
},
"likes": 0
}
]
}
With this last step, we’re done adding the necessary mock data to start building out the application feed.
Time to write some GraphQL!
GraphQL
inside your src
directory.Queries.js
inside the new directory src/GraphQL
.import { gql } from "@apollo/client";
export const QUERY_LOGGED_IN_USER = gql`
query getUserInfo($userFilter: UserFilter!) {
queryUser(filter: $userFilter) {
email
following {
username
avatarImageURL
posts {
id
imageURL
description
likes
postedBy {
username
avatarImageURL
}
}
}
}
}
`;
The gql
is a template literal. With this, you can write GraphQL queries inside your JavaScript code. Later, it parses the query into the GraphQL AST, which both Apollo and the server can understand.
The query should be very familiar to you. And don’t worry, we’ll now discuss how to actually “execute” this query, and supply the query variable!
Feed.js
in the src/Components
directory. This file will hold a Feed
component, in charge of rendering the application feed.import { useQuery } from "@apollo/client";
import { QUERY_LOGGED_IN_USER } from "../GraphQL/Queries/Queries";
import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
import {
Spinner,
Text,
Card,
CardBody,
Heading,
Avatar,
CardHeader,
Box,
Grid,
Image,
CardFooter,
} from "grommet";
Feed
component.const Feed = () => {
const { user } = useAuth0();
const { email } = user;
const { data, loading, error } = useQuery(QUERY_LOGGED_IN_USER, {
variables: {
userFilter: {
email: {
eq: email,
},
},
},
});
if (loading) {
return <Spinner />;
}
if (error) {
return (
<div>
<Text size="large" color="red">
{error.message}
</Text>
</div>
);
}
return (
<Box pad="large" direction="row" alignSelf="center">
<Grid
gap="large"
rows="medium"
columns={{ count: "fit", size: ["small", "medium"] }}
>
{data.queryUser.map((userInfo) =>
userInfo.following.map((followedUser) =>
followedUser.posts.map((post) => (
<Card width="medium" key={post.id}>
<CardHeader
pad={{ horizontal: "small", vertical: "small" }}
background="light-1"
width="medium"
justify="stretch"
gap="small"
>
{/* Avatar of the user */}
<Avatar
size="small"
src={post.postedBy.avatarImageURL}
a11yTitle="Generated avatar for the user from robohash.org"
/>
<Heading level="4" margin="none">
{post.postedBy.username}
</Heading>
</CardHeader>
{/* The image of the post*/}
<CardBody height="medium">
<Image fit="cover" src={post.imageURL} />
</CardBody>
<CardFooter
pad={{ horizontal: "small" }}
height="xxsmall"
background="light-3"
justify="between"
gap="xxsmall"
>
<Text size="small">{post.description}</Text>
<Heading level="5" margin="none">
{post.likes == 1
? `${post.likes} like`
: `${post.likes} likes`}
</Heading>
</CardFooter>
</Card>
))
)
)}
</Grid>
</Box>
);
}
export default withAuthenticationRequired(Feed, {
onRedirecting: () => <Spinner />,
});
What’s happening here?
user
object from the useAuth0
hook. This object contains information about the authenticated user, like name, email, and so on.email
parameter from the user
object. This is what we need for now in our GraphQL query.useQuery
hook is what Apollo offers for making GraphQL queries. The hook takes many options, but we’re only making use of two of them.
useAuth0
hook.useQuery
returns an object containing the result.
loading
property indicates a loading
state when the data hasn’t come through yet.error
contains any error that might have occurred in the process.data
property contains the actual data after a successful response.Spinner
component to visually represent a “loading” state in the app.map
through the JSON response tree. Using Grommet’s UI components, we render the details of felix
’s posts. The details we expose are:
Feed
component to someone who’s not logged in. For this purpose, we use the higher-order component called withAuthenticationRequired
.
Spinner
component, as it redirects the user to the login page.You should see the new Feed
component in action when you visit the app:
Yay! Our toy Instagram clone can now make authenticated requests to the backend, fetch data, and use that to render a feed.
Today we learned how to set up Apollo Client in a React application and use it to perform GraphQL queries. We also learned how to configure the client for authentication over HTTP. We improved upon the schema of the app and built more of the UI.
With the Dgraph platform taking care of the backend and the API, we can focus completely on learning and developing the app. There are no distractions, and we’re able to iterate faster to meet our goals. This becomes an even more powerful incentive in business-level, large-scale products.
Stay tuned for the upcoming deep-dives into more concepts and exciting stuff.
Below are some more resources that might help you regarding today’s discussion: