Building a Simple Chat with React GraphQL and Hasura effortlessly - Part 1

In this article, we will see how to build a chat application concept using react graphQL and Hasura. Building a Chat application with react graphql and Hasura.

Recent Articles,

Building Real time API using graphql subscriptions

TypeScript for React developers in 2020

Building a Piano with React Hooks

Demo

Complete Source code can be found here

Hasura Setup

Hasura is basically used to create a backend in less time. you don't need to setup a node server develop a graphql from scratch.

Hasura does that part for you. only thing you need to do is, create a postgres tables according to your application requirement.then, Hasura created all the GraphQL Queries,Mutations and Subscriptions for you.

Let's see how to setup hasura for our chat application. Setting up hasura is so easy on Heroku.

Once you deploy your app in heroku. it will take you to your heroku dashboard.

If you watch it carefully, Postgres database is already installed on your app.

Now it is time to create some postgres tables. click on Open App in your heroku dashboard. it will take you to your hasura console.

After that, we need to create tables for our application.

users - create users table to store all the users for our chat application.

- create users table to store all the users for our chat application. message - message table stores all the messages of our application.

Once you create the tables, Hasura auto generates all the GraphQL Queries,Mutations and Subscriptions for you.

All you need to do is, use mutations and queries to insert and fetch the data from postgres database.

Our application backend work is done. So, easy!!! isn't it?.

React GraphQL Setup

Let's see how to setup react graphql for our application.

1 npx create-react-app hasura-chat

Think about what are all components that we need for our chat application. Mainly we need a login page and chat elements. since it is a list we need to create a chat item as a component.

Login - this component renders the login page.

- this component renders the login page. Chat - it renders the chat items as a list.

- it renders the chat items as a list. ChatItems - for each item in the chat, it renders the chat items component.

Everything is good so far. but wait, how are we going to handle the api call and data in react.

well, that is where we are going to use GraphQL here. i have to say, i tried two to three solutions for GraphQL. but, i was not satisfied until i found the current solution.

we are going to use Apollo react hooks for our API call. From my learning and experience, it is so far the easiest way to get started with GraphQL on react applications.

Okay. Enough talking. Let's get started with setting it up.

1 npm install @apollo/react-hooks apollo-link-ws apollo-link-http apollo-link apollo-utilities apollo-cache-inmemory apollo-boost

Wow..that's a lot of packages. Let's break it down one by one.

@apollo/react-hooks - it is used to handle all the graphql queries,mutation and subscriptions using react hooks.

apollo-link-ws - this package is used to connect web socket with our backend.

apollo-link-http - it is used to connect http with our backend.

apollo-link - it act as a network interface for graphql request and fetching the result. Here, apollo link is used to split the http and web socket connections.

apollo-utilities - this package is used get the information of graphql queries.

apollo-cache-memory - it is used for caching the graphql results.

After that, we need to set it up in our App.js. import all the packages in App.js.

1 import ApolloClient from "apollo-client" 2 import { ApolloProvider } from "@apollo/react-hooks" 3 import { WebSocketLink } from "apollo-link-ws" 4 import { HttpLink } from "apollo-link-http" 5 import { split } from "apollo-link" 6 import { getMainDefinition } from "apollo-utilities" 7 import { InMemoryCache } from "apollo-cache-inmemory"

create http and websocket link with your hasura graphql endpoint

1 const httpLink = new HttpLink ( { 2 uri : "https://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql" , 3 } ) 4 5 6 const wsLink = new WebSocketLink ( { 7 uri : "ws://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql" , 8 options : { 9 reconnect : true , 10 } , 11 } )

Then, create a Apollo client for our application.

1 2 3 const link = split ( 4 5 ( { query } ) => { 6 const { kind , operation } = getMainDefinition ( query ) 7 return kind === "OperationDefinition" && operation === "subscription" 8 } , 9 wsLink , 10 httpLink 11 ) 12 13 14 const client = new ApolloClient ( { 15 link , 16 cache : new InMemoryCache ( ) , 17 } )

finally, import it in our Apollo Provider

1 function App ( ) { 2 return ( 3 < ApolloProvider client = { client } > 4 < ThemeProvider theme = { customTheme } > 5 < div className = "App" > 6 < Routes / > 7 < / div > 8 < / ThemeProvider > 9 < / ApolloProvider > 10 ) 11 }

React Components

Like we discussed in the beginning, let's create react components for our application.

create the components and routes for each components. Here, we have two routes, they are

/login - renders the Login Flow

/chat - renders the Chat Component

React Hooks

Here, we use two important react hooks for graphql data fetch/insert. they are,

useMutation - useMutation is used for GraphQL mutation in our backend.

useSubscription - Since we are building a real time app, we need GraphQL Subscriptions. useSubscription is used to create a web socket link.

useMutation

we use useMutation hooks in Login Component here. In login/index.js add the following code,

1 import { useMutation } from "@apollo/react-hooks" 2 import { gql } from "apollo-boost"

we import useMutation and gql from react hooks and apollo boost to make a GraphQL request.

1 const LOGIN_USER = gql ` 2 mutation InsertUsers($name: String!, $password: String!) { 3 insert_users(objects: { name: $name, password: $password }) { 4 returning { 5 id 6 name 7 } 8 } 9 } 10 `

After that, we create a graphql mutation and store it in a constant LOGIN_USER

Then, inside our login component, define the useMutation react hook.

1 const [ insert_users , { data } ] = useMutation ( LOGIN_USER )

when use clicks the login button, call the insert_users function with required data.

1 const onSubmit = ( ) => { 2 insert_users ( { variables : { name : state . name , password : state . password } } ) 3 }

it will return the success response. store it in your component state and use it for the next step.

1 import React , { useState , useEffect } from "react" 2 import { useForm } from "react-hook-form" 3 import { 4 FormErrorMessage , 5 FormLabel , 6 FormControl , 7 Input , 8 Button , 9 Box , 10 } from "@chakra-ui/core" 11 12 import { useMutation } from "@apollo/react-hooks" 13 import { gql } from "apollo-boost" 14 15 const LOGIN_USER = gql ` 16 mutation InsertUsers($name: String!, $password: String!) { 17 insert_users(objects: { name: $name, password: $password }) { 18 returning { 19 id 20 name 21 } 22 } 23 } 24 ` 25 26 const Login = ( { history } ) => { 27 const [ state , setState ] = useState ( { 28 name : "" , 29 password : "" , 30 } ) 31 32 const [ insert_users , { data } ] = useMutation ( LOGIN_USER ) 33 34 useEffect ( ( ) => { 35 const user = data && data . insert_users . returning [ 0 ] 36 if ( user ) { 37 localStorage . setItem ( "user" , JSON . stringify ( user ) ) 38 history . push ( "/chat" ) 39 } 40 } , [ data ] ) 41 const { handleSubmit , errors , register , formState } = useForm ( ) 42 43 function validateName ( value ) { 44 let error 45 if ( ! value ) { 46 error = "Name is required" 47 } 48 return error || true 49 } 50 51 function validatePassword ( value ) { 52 let error 53 if ( value . length <= 4 ) { 54 error = "Password should be 6 digit long" 55 } 56 57 return error || true 58 } 59 60 const onInputChange = e => { 61 setState ( { ... state , [ e . target . name ] : e . target . value } ) 62 } 63 64 const onSubmit = ( ) => { 65 insert_users ( { variables : { name : state . name , password : state . password } } ) 66 67 setState ( { name : "" , password : "" } ) 68 } 69 70 return ( 71 < Box > 72 < form onSubmit = { handleSubmit ( onSubmit ) } > 73 < FormControl isInvalid = { errors . name } > 74 < FormLabel htmlFor = "name" > Name < / FormLabel > 75 < Input 76 name = "name" 77 placeholder = "name" 78 onChange = { onInputChange } 79 ref = { register ( { validate : validateName } ) } 80 / > 81 < FormErrorMessage > 82 { errors . name && errors . name . message } 83 < / FormErrorMessage > 84 < / FormControl > 85 86 < FormControl isInvalid = { errors . password } > 87 < FormLabel htmlFor = "name" > Password < / FormLabel > 88 < Input 89 name = "password" 90 type = "password" 91 placeholder = "password" 92 onChange = { onInputChange } 93 ref = { register ( { validate : validatePassword } ) } 94 / > 95 < FormErrorMessage > 96 { errors . password && errors . password . message } 97 < / FormErrorMessage > 98 < / FormControl > 99 < Button 100 mt = { 4 } 101 variantColor = "teal" 102 isLoading = { formState . isSubmitting } 103 type = "submit" 104 > 105 Submit 106 < / Button > 107 < / form > 108 < / Box > 109 ) 110 } 111 112 export default Login

useSubscription

similary define the useSubscription inside the Chat/index.js to get the data through web sockets.

1 import { useMutation , useSubscription } from "@apollo/react-hooks" 2 import { gql } from "apollo-boost"

we import useMutation, useSubscription and gql in the Chat component.

1 const MESSAGES_SUBSCRIPTION = gql ` 2 subscription { 3 messages { 4 id 5 text 6 users { 7 id 8 name 9 } 10 } 11 } 12 ` 13 14 const SUBMIT_MESSAGES = gql ` 15 mutation InsertMessages($text: String!, $userid: Int!) { 16 insert_messages(objects: { text: $text, created_user: $userid }) { 17 returning { 18 text 19 created_user 20 users { 21 name 22 id 23 } 24 id 25 } 26 } 27 } 28 `

Here, we have two graphql request. one is for Mutation and another one is for subscription.

After that, define the hooks in Chat component.

1 const [ insert_messages , { returnData } ] = useMutation ( SUBMIT_MESSAGES ) 2 3 const { loading , error , data : { messages } = [ ] } = useSubscription ( 4 MESSAGES_SUBSCRIPTION 5 )

messages from subscription returns the updated data through websocket. so, use the data in react render for real time data.

1 import React , { useState , useEffect } from "react" 2 3 import { Box , Flex , Input } from "@chakra-ui/core" 4 5 import ChatItem from "../ChatItem" 6 7 import { useMutation , useSubscription } from "@apollo/react-hooks" 8 import { gql } from "apollo-boost" 9 10 const MESSAGES_SUBSCRIPTION = gql ` 11 subscription { 12 messages { 13 id 14 text 15 users { 16 id 17 name 18 } 19 } 20 } 21 ` 22 23 const SUBMIT_MESSAGES = gql ` 24 mutation InsertMessages($text: String!, $userid: Int!) { 25 insert_messages(objects: { text: $text, created_user: $userid }) { 26 returning { 27 text 28 created_user 29 users { 30 name 31 id 32 } 33 id 34 } 35 } 36 } 37 ` 38 39 const Chat = ( ) => { 40 const [ state , setState ] = useState ( { 41 text : "" , 42 } ) 43 44 const [ insert_messages , { returnData } ] = useMutation ( SUBMIT_MESSAGES ) 45 46 const { loading , error , data : { messages } = [ ] } = useSubscription ( 47 MESSAGES_SUBSCRIPTION 48 ) 49 50 const onInputChage = e => { 51 setState ( { [ e . target . name ] : e . target . value } ) 52 } 53 54 const onEnter = e => { 55 if ( e . key === "Enter" ) { 56 let user = localStorage . getItem ( "user" ) 57 user = JSON . parse ( user ) 58 59 insert_messages ( { variables : { text : state . text , userid : user . id } } ) 60 61 setState ( { text : "" } ) 62 } 63 } 64 65 return ( 66 < Box h = "100vh" w = "40%" margin = "auto" > 67 < Flex direction = "column" h = "100%" > 68 < Box bg = "blue" h = "90%" w = "100%" border = "solid 1px" overflowY = "scroll" > 69 { messages && 70 messages . map ( message => { 71 return < ChatItem item = { message } / > 72 } ) } 73 < / Box > 74 < Box bg = "green" h = "10%" w = "100%" > 75 < Input 76 placeholder = "Enter a message" 77 name = "text" 78 value = { state . text } 79 onChange = { onInputChage } 80 onKeyDown = { onEnter } 81 size = "md" 82 / > 83 < / Box > 84 < / Flex > 85 < / Box > 86 ) 87 } 88 89 export default Chat

ChatItem.js

1 import React from "react" 2 3 import { Box , Flex , Avatar , Heading , Text } from "@chakra-ui/core" 4 5 const ChatItem = ( { item } ) => { 6 return ( 7 < Box h = "60px" > 8 < Flex direction = "row" alignItems = "center" height = "100%" > 9 < Avatar size = "sm" padding = "4px" marginLeft = "10px" / > 10 < Flex direction = "column" margin = "5px" > 11 < Text fontSize = "xl" margin = "0" > 12 { item . users . name } 13 < / Text > 14 < Text margin = "0" > { item . text } < / Text > 15 < / Flex > 16 < / Flex > 17 < / Box > 18 ) 19 } 20 21 export default ChatItem

Summary

So far, we have seen an application development using Hasura and Postgres on backend with React GraphQL and Apollo for front end.

In the upcoming article, we will see how to build a paginated api in Hasura and apollo react hooks and Infinite loader in react application. So stay tuned.

Until then, Happy Coding :-)

To Read More