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.

Screenshot 2020 03 14 at 12 13 49 PM

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.

Screenshot 2020 03 14 at 12 15 50 PM

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.

Screenshot 2020 03 14 at 12 18 56 PM

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

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

hasura chat

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

react graphql

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.

1npx 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.
  • Chat - it renders the chat items as a list.
  • ChatItems - for each item in the chat, it renders the chat items component.

hasura chat Page 2

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.

1npm 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.

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

create http and websocket link with your hasura graphql endpoint

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

Then, create a Apollo client for our application.

1// using the ability to split links, you can send data to each link
2// depending on what kind of operation is being sent
3const link = split(
4 // split based on operation type
5 ({ query }) => {
6 const { kind, operation } = getMainDefinition(query)
7 return kind === "OperationDefinition" && operation === "subscription"
8 },
9 wsLink,
10 httpLink
11)
12
13// Instantiate client
14const client = new ApolloClient({
15 link,
16 cache: new InMemoryCache(),
17})

finally, import it in our Apollo Provider

1function 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.

Screenshot 2020 03 14 at 1 27 42 PM

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,

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

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

1const 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.

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

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

1const 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.

1import React, { useState, useEffect } from "react"
2import { useForm } from "react-hook-form"
3import {
4 FormErrorMessage,
5 FormLabel,
6 FormControl,
7 Input,
8 Button,
9 Box,
10} from "@chakra-ui/core"
11
12import { useMutation } from "@apollo/react-hooks"
13import { gql } from "apollo-boost"
14
15const 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
26const 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
112export default Login

useSubscription

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

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

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

1const MESSAGES_SUBSCRIPTION = gql`
2 subscription {
3 messages {
4 id
5 text
6 users {
7 id
8 name
9 }
10 }
11 }
12`
13
14const 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.

1const [insert_messages, { returnData }] = useMutation(SUBMIT_MESSAGES)
2
3const { 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.

1import React, { useState, useEffect } from "react"
2
3import { Box, Flex, Input } from "@chakra-ui/core"
4
5import ChatItem from "../ChatItem"
6
7import { useMutation, useSubscription } from "@apollo/react-hooks"
8import { gql } from "apollo-boost"
9
10const MESSAGES_SUBSCRIPTION = gql`
11 subscription {
12 messages {
13 id
14 text
15 users {
16 id
17 name
18 }
19 }
20 }
21`
22
23const 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
39const 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
89export default Chat

ChatItem.js

1import React from "react"
2
3import { Box, Flex, Avatar, Heading, Text } from "@chakra-ui/core"
4
5const 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
21export 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

Building a Piano with React Hooks

In this article, we will see how to build a piano with react hooks. Building a P...

TypeScript Basics - The Definitive ...

In this article, we will learn some basics of typescript which helps you to deve...

Here's why podman is more secured t...

In this article we will see about podman and why it is more secured way to run c...

Building a Production - Ready Node....

In this article, we will see how to build a Nodejs, TypeScript Application and d...

Nginx for Front-end Developers

This article is to explain Nginx for Front-end Developers in a much simpler way....

What is gRPC ? How to implement gRP...

Everyone talks about gRPC. Have you ever wonder how it works or how to implement...