Building a Hacker News Clone with GraphQL, Hasura and React - Part 2
This tutorial was written by Abhijeet Singh and published as part of the Hasura Technical Writer Program - an initiative that supports authors who write guides and tutorials for the open source Hasura GraphQL Engine.
In part-1 of this series, we setup our backend and Auth0. In this part, we will setup our React app and connect it to our backend.
React App Setup
We will start with user authentication. We will be using JWT (JSON web tokens) for authentication. Let’s first create some basic header in our react app for showing login button.
Replace the contents of styles/index.css
file as shown:
These styles will be used throughout our app so you don’t have to worry about the styling.
Setup Apollo GraphQL Client
Replace contents of App.js
to use Apollo GraphQL client as shown below. (See apollo github repository for more help).
In line 15
change the uri
to your GraphQL Endpoint on Hasura, which you can find on hasura console (remember where you created tables). Here we have imported the header
component which we will implement now.
Create header component and use react-router
We will use react-router
to implement single-page application behaviour. Install react-router
using:
$ npm install react-router-dom
React Router, and dynamic, client-side routing, allows us to build a single-page web application with navigation without the page refreshing as the user navigates. React Router uses component structure to call components, which display the appropriate information.
By preventing a page refresh, and using Router or Link, the flash of a white screen or blank page is prevented. This is one increasingly common way of having a more seamless user experience. (source)
For using react-router
in our app, we have to wrap whole app in BrowserRouter
It is a context provider for routing, which provides several props
necessary for routing (like match
, location
, history
). See this if you are unfamiliar with context. Replace the contents of index.js:
Next, we will create a header
component for navigation within app. Create a header.js
file in components
directory. The contents of header.js
should be:
Here we are creating a navbar similar to HackerNews navbar. Later we will add some routes to it for navigation. That’s it! We have successfully created a header navbar and used react-routes
in our app.
Auth0 JWT integration with React App
Follow along with Auth0-react-quickstart guide as reference to include Auth0 in react app. Configure Auth0 client by setting Allowed Callback URLs
, Allowed Web Origins
, Allowed Logout URLs
to http://localhost:3000 and add the custom API if you haven’t done already. Now install auth0-spa-js
:
$ npm install @auth0/auth0-spa-js
Now we will include react-auth0-wrapper
in our app, which is a set of custom react-hooks that enable you to work with the Auth0 SDK. Create a new directory src/auth
and add file react-auth0-wrapper.js
populate it with code from here.
Now add another file as auth/auth_config.json
in src/auth
. Populate auth_config.json
with following code (change the values accordingly):
Now we are ready to include login functionality in our react app. Basically, we will be including a login
button in header. This button will lead to login through Auth0 with redirect to our localhost
once login/signup is completed. At the same time login/signup data will be updated in our users
table in hasura backend due to the Auth0 rules
we added earlier. Once the login is done, we will get the accessToken
in JWT format using functions provided by Auth0 SDK in App.js
. This accessToken
will then be used as a authorization header in apollo client queries to backend, thus every query which goes to backend will have authorization header.
Firstly, change the contents of index.js to the following:
Here, we are using the Auth0Provider
which is a context provider for Auth0 client. Any children components will now have access to the Auth0 client.
Having provided the Auth0 client to our app, we now replace the contents of components/header.js
file to the following:
We are using useState
hook(line 20) to set initial accessToken
value to empty string. If the user is logged in, the token is fetched from the Aut0 SDK client using getTokenSilently()
(line 31). Notice that this function returns a Promise
and is asynchronous. This function attempts to return the current access token. If the token is invalid, the token is refreshed silently before being returned from the function. If thetry
block successfully gets executed, accessToken
value is set to the JWT access-token from Auth0 (line 32).
The component re-renders when we get accessToken
value. Thus after the async function has finished executing, we store the value of accessToken
in state. The component re-renders and apollo-client gets the token value, thus re-rendering the whole ApolloProvider
(context-provider) with new token value and the authentication header.
Once we have accessToken, we will use this to make requests to our backend using apollo client. See apollo-docs for apollo authentication using headers. Basically, we are passing the accessToken
as authorization header(line 50), in our apollo queries. This client is then used inside the ApolloProvider
(context provider) to provide the child elements access to apollo client created here.
Now, you should be able to login and logout of our app. Clear cache and login. You must be asked to give access to your auth0 tenant by our hasura backend. Give the access and you’re good to go.
Note : If you are facing errors, remember to not keep any dependency onreact-apollo
.@apollo/react-hooks
must be used instead.
Implementing Post List and Upvote/Points(realtime updates of upvotes)
We will implement a list of post and upvote post button. Create a new component components/PostList.js
as :
POSTS_LIST
query(line 10) is being used to fetch details from post
table in our database. We can fetch post description, url, id, user details who created the post, and also the number of upvotes/points in that post all in just one query. This is due to the object and array relationships we created on our post
table earlier. useQuery
(line 31) is a custom apollo-client react hook. We get the query data in data
object (line 31) which is then passed as a prop to the Post
component, which we will implement now.
Create a new component components/Post.js
as:
Two things are happening here: we are displaying the post information using props passed by parent component, and we are writing a GraphQL mutation o upvote posts.
Firstly, we are getting post information in props
(line 18). This is used to display post description
, url
, points count
and created_at
.
Secondly, we are making a mutation UPVOTE_POST
using useMutation
hook. For upvoting a post, we need current logged in user’s userId
and postId
. userId
is present in user
object we get from useAuth0
hook (line19), which is being stored in loggedUserId
variable (line 23). Once we get userId
we will use it as variable in our mutation. See apollo mutation if you are unfamiliar with passing variables in mutation. The other variable in useMutation
hook, refetchQueries
is used to refetch the specified query after the mutation results have arrived. Thus, upvotes/points will be recorded and shown in real-time.
The mutation will insert a row in our point
table, recording the upvote. Now, the same user cannot vote the given post again as (post_id
+ user_id
) is the unique key for our point table, which we set earlier.
Finally, we need to import PostList
component in App.js
. Change your App.js
file to make the following changes:
Switch
is a part of react-router which is used to match components with their paths.
Try upvoting posts, and see the real-time updates in upvotes, thanks to refetchQueries
. We haven’t yet implemented the user-profile and create-post functionality, so the submit button and user-profile links do not work. Next we will implement the same.
Implementing Submit Post functionality
Create a new file as components/SecuredRoute.js
as:
This will help us to create some secured routes which can only be accessed if the user is logged-in. We will use secured routes while routing. Using secured route, if someone tries to access the url’s without logging-in, user will be redirected to login automatically.
Now, let’s create component components/NewPost.js
as:
This component will be used to submit a post. We have a form
which has two input fields for description
and url
. We are using react state to store values of description
, url
and error
(if mutation is not successful). The mutation accepts three variables which are being passed using variables
parameter (line 34). If the mutation succeeds, we are using refetchQueries
to refetch POSTS_LISTS
query, thus implementing real-time updates of the list of post on the home page. If the mutation is successful, data in our backend postgres database gets updated with the new post data.
Let’s include route to new-post
in our App.js
:
It is a secured route, so if we try to access localhost:3000/new-post
without logging-in, we will be redirect to login screen.
Now, you can try submitting a post and see that the home page will be updated with the latest post. In case of any error in mutation, the error message will be shown on screen.
Implementing User Profile
Let’s now implement user-profile. Create a new component components/Profile.js
as :
We are getting user_id
as props which will be used to query our backend database for user info, for the given user_id
. The data is then rendered in return()
. The props (user_id
) here is being passed in form of url, and we are using props.match.params.id
to get that prop. These props are provided by the react-router BrowserRouter
context provider, which is included in our index.js
file.
We need to now update App.js
to redirect user to profile section:
Here, we are passing id
as props (path={“/user/:id”}
), which is accessed via props.match.params.id
. This is a react-router way of passing prop. See this example for more details.
Finally, we have our app ready with user-profiles section. You can navigate to user-profiles, create new posts and see real-time updates of new-posts, upvote posts and see real-time updates of upvotes.
You should now have a working Hacker News clone. Incase you'd like to reference it, the final code for this app is hosted here. Visit the live demo here.
About the author
Abhijeet Signh is final year UG student in Computer Science and Engineering from IIIT Kalyani. He has done work in Full Stack Development, Android, Deep Learning, Machine Learning and NLP. He actively takes part in competitive programming contests and has interest in solving algorithmic problems. He is a startup enthusiast and plays table tennis and guitar in spare time.