AWS AppSync and Amplify with React & GraphQL - Complete Steps
Tags: aws, react, graphql
Published: 2021-04-07 08:09:54

In this post, I will try to explain the steps needed to develop a production-ready serverless backend API powered by AWS AppSync and GraphQL that scales to MILLIONS of requests. And as a bonus, you will also learn how to develop Real-Time Subscriptions with AWS AppSync for real-time scalable updates for your web applications

## Components Used
This gist uses the following frameworks or libraries:
- GraphQL
- React
- Node.js and NPM

This gist uses the following AWS services: 
- AWS AppSync (_For GraphQL_)
- Amplify (_For backend_)
- DynamoDB (_For Database_)
- Amazon Cognito (_For Authentication_)

---
## Steps
#### Step 1 : 
To install `aws-amplify` cli tools:
```js
npm install -g @aws-amplify/cli
```
#### Step 2 : 
To create a React App:
```js
npx create-react-app AppName
```
#### Step 3 :
To start the react dev server on localhost:
```js
npm start
```
#### Step 4 : 
To initialize the AWS Amplify within the project:
```js
amplify init
```
Then prompts will appear for necessary information about the project:
- App Name
- Environment Name
- IDE
- Framework
- language
- src folder
- Aws-Profile-> Yes
- select profile (Default)

#### Step 5 : 
To add AWS Appsync:
```js
amplify add api
```
Then prompts will appear for necessary information:
- GraphQL
- API Key or User Pool
- have schema -> No
- need Guided schema -> Yes
- single object with fields (for starter)
- create schema now -> Yes
- enter to continue

#### Step 6 : 
To push these settings to AWS cloud:
```js
amplify push
```
Then prompts will appear:
- continue -> Yes
- generate code -> Yes
- js
- generate all -> Yes
- max depth -> 2 is fine

#### Step 7 : 
Go to Appsync console from command:
```sh
amplify console api
```
#### Step 8 : 
To install AWS modules for working with react:
```sh
npm install aws-amplify aws-amplify-react
```
#### Step 9 : 
To configure AWS in the react app:
In the `index.js` file in the `src` folder of React App:
```js
import Amplify from 'aws-amplify'
import aws_exports from './aws-exports'

Amplify.configure(aws_exports)
```
---
## Usage
#### Step 10 :
To use GraphQL queries within react: 
```js
import { <queryName> } from '../graphql/queries'
import { API, graphqlOperation } from 'aws-amplify'

class ComponentName extends Component {
    componentDidMount = async () => {
            this.getPosts()
    }
    
    getPosts = async () => {
         const result = await API.graphql(graphqlOperation(listPosts))

         this.setState({ posts: result.data.listPosts.items})
          //console.log("All Posts: ", JSON.stringify(result.data.listPosts.items))
          //console.log("All Posts: ", result.data.listPosts.items)
    }
}

```
#### Step 11 : 
To add Subscriptions to automatically listen to changes:
```js
componentDidMount = async () => {
    this.getPosts()

    this.createPostListener = API.graphql(graphqlOperation(onCreatePost))
        .subscribe({
            next: postData => {
              const newPost = postData.value.data.onCreatePost
              const prevPosts = this.state.posts.filter( post => post.id !== newPost.id)

              const updatedPosts = [newPost, ...prevPosts]

              this.setState({ posts: updatedPosts})
            }
        })

    componentWillUnmount() {
        this.createPostListener.unsubscribe()
    }

    getPosts = async () => {
         const result = await API.graphql(graphqlOperation(listPosts))

         this.setState({ posts: result.data.listPosts.items})
          //console.log("All Posts: ", JSON.stringify(result.data.listPosts.items))
          //console.log("All Posts: ", result.data.listPosts.items)
    }
```
#### Step 12 : 
To add Authentication using Amazon Cognito:
```js
amplify add auth
```
Prompts:
- Default configuration
- username/email
- No, I am done 
#### Step 13 : 
```js
amplify push
```
#### Step 14 : 
in the `App.js` file:
```js
import { withAuthenticator } from 'aws-amplify-react'

export default withAuthenticator(App, { includeGreetings: true });
```
#### Step 15 : 
```js
componentDidMount = async () => {
    this.getPosts()

    await Auth.currentUserInfo()
        .then(user => {
            this.setState(
                {
                    ownerId: user.attributes.sub,
                    ownerUsername: user.username,
                }
            )
        })
}
```
#### Step 16 : 
Complete example of CRUD of a blogpost with like, comment, edit, delete:
```js
import React, { Component } from 'react'
import { listPosts } from '../graphql/queries'
import { API, graphqlOperation } from 'aws-amplify'
import DeletePost from './DeletePost'
import EditPost from './EditPost'
import { onCreatePost, onDeletePost, onUpdatePost, onCreateComment, onCreateLike } from '../graphql/subscriptions'
import { createLike } from '../graphql/mutations'
import CreateCommentPost from './CreateCommentPost'
import CommentPost from './CommentPost'
import { FaThumbsUp, FaSadTear } from 'react-icons/fa';
import {Auth} from 'aws-amplify'
import UsersWhoLikedPost from './UsersWhoLikedPost'

class DisplayPosts extends Component {

    state = {
        ownerId:"",
        ownerUsername:"",
        errorMessage: "",
        postLikedBy: [],
        isHovering: false,
        posts: []
    }

    componentDidMount = async () => {
        this.getPosts()

        await Auth.currentUserInfo()
            .then(user => {
                this.setState(
                    {
                        ownerId: user.attributes.sub,
                        ownerUsername: user.username,
                    }
                )
            })


        this.createPostListener = API.graphql(graphqlOperation(onCreatePost))
             .subscribe({
                 next: postData => {
                      const newPost = postData.value.data.onCreatePost
                      const prevPosts = this.state.posts.filter( post => post.id !== newPost.id)
                      const updatedPosts = [newPost, ...prevPosts]
                      this.setState({ posts: updatedPosts})
                 }
             })

        this.deletePostListener = API.graphql(graphqlOperation(onDeletePost))
                .subscribe({
                     next: postData => {
                        const deletedPost = postData.value.data.onDeletePost
                        const updatedPosts = this.state.posts.filter(post => post.id !== deletedPost.id)
                        this.setState({posts: updatedPosts})
                     }
                })

        this.updatePostListener = API.graphql(graphqlOperation(onUpdatePost))
                .subscribe({
                     next: postData => {
                          const { posts } = this.state
                          const updatePost = postData.value.data.onUpdatePost
                          const index = posts.findIndex(post => post.id === updatePost.id) //had forgotten to say updatePost.id!
                          const updatePosts = [
                              ...posts.slice(0, index),
                             updatePost,
                             ...posts.slice(index + 1)
                            ]

                            this.setState({ posts: updatePosts})

                     }
                })

            this.createPostCommentListener = API.graphql(graphqlOperation(onCreateComment))
                .subscribe({
                     next: commentData => {
                          const createdComment = commentData.value.data.onCreateComment
                          let posts = [ ...this.state.posts]

                          for (let post of posts ) {
                               if ( createdComment.post.id === post.id) {
                                    post.comments.items.push(createdComment)
                               }
                          }
                          this.setState({ posts})
                     }
                })

                this.createPostLikeListener = API.graphql(graphqlOperation(onCreateLike))
                    .subscribe({
                         next: postData => {
                              const createdLike = postData.value.data.onCreateLike
                              let posts = [...this.state.posts]
                              for (let post of posts ) {
                                   if (createdLike.post.id === post.id) {
                                        post.likes.items.push(createdLike)
                                   }
                              }
                              this.setState({ posts })
                         }
                    })
    }


    componentWillUnmount() {
        this.createPostListener.unsubscribe()
        this.deletePostListener.unsubscribe()
        this.updatePostListener.unsubscribe()
        this.createPostCommentListener.unsubscribe()
        this.createPostLikeListener.unsubscribe()
    }

    getPosts = async () => {
         const result = await API.graphql(graphqlOperation(listPosts))

         this.setState({ posts: result.data.listPosts.items})
          //console.log("All Posts: ", JSON.stringify(result.data.listPosts.items))
          //console.log("All Posts: ", result.data.listPosts.items)
    }

    likedPost = (postId) =>  {
         
        for (let post of this.state.posts) {
              if ( post.id === postId ) {
                   if ( post.postOwnerId === this.state.ownerId) return true;
                    for (let like of post.likes.items) {
                         if (like.likeOwnerId === this.state.ownerId) {
                             return true;
                         }
                    }
              }
        }
        return false;
    }

    handleLike = async postId => {
         if (this.likedPost(postId)) {return this.setState({errorMessage: "Can't Like Your Own Post."})} else {
            const input = {
                numberLikes: 1,
                likeOwnerId: this.state.ownerId,
                likeOwnerUsername: this.state.ownerUsername,
                likePostId: postId
           }
           
           try {
              const result =  await API.graphql(graphqlOperation(createLike, { input }))
               console.log("Liked: ", result.data);
           }catch (error) {
                console.error(error)
           }
         }
    }

    handleMouseHover = async postId => {
         this.setState({isHovering: !this.state.isHovering})

         let innerLikes = this.state.postLikedBy

         for (let post of this.state.posts) {
              if (post.id === postId) {
                   for ( let like of post.likes.items) {
                         innerLikes.push(like.likeOwnerUsername)
                   }
              }
              this.setState({postLikedBy: innerLikes})
         }

          console.log("Post liked by: ", this.state.postLikedBy);
    }

    handleMouseHoverLeave = async () => {
            this.setState({isHovering: !this.state.isHovering})
            this.setState({postLikedBy: []})
    }
    
    render() {
        const { posts } = this.state
         
        let loggedInUser = this.state.ownerId

        return posts.map(( post ) => {

             return (
                  <div className="posts"  style={rowStyle} key={ post.id}> 
                    <h1> { post.postTitle }</h1>
                    <p> { post.postBody }</p>

                    <br />
                    <span>
                        {post.postOwnerId === loggedInUser &&
                            <DeletePost data={post}/>
                        }
                         
                        { post.postOwnerId === loggedInUser &&
                           <EditPost {...post} />
                        }
                         <span>
                             <p className="alert">{ post.postOwnerId === loggedInUser && this.state.errorMessage}</p>
                             <p onMouseEnter={ () => this.handleMouseHover(post.id)}
                                 onMouseLeave={ () => this.handleMouseHoverLeave()}
                                 onClick={() => this.handleLike(post.id)}
                                  style={{color: (post.likes.items.length > 0) ? "blue": "gray"}}
                                 className="like-button"> 
                                <FaThumbsUp /> 
                               {post.likes.items.length}
                             </p>
                             {
                                  this.state.isHovering &&
                                    <div className="users-liked">
                                         {this.state.postLikedBy.length === 0 ? 
                                               " Liked by No one " : "Liked by: " }
                                         {this.state.postLikedBy.length === 0 ? <FaSadTear /> : <UsersWhoLikedPost data={this.state.postLikedBy} /> }

                                    </div>
                             }

                         </span>
                    </span>

                    <span>
                        <CreateCommentPost postId={post.id} />
                        { post.comments.items.length > 0 && <span style={{fontSize:"19px", color:"gray"}}>
                             Comments: </span>}
                             {
                                  post.comments.items.map((comment, index) => <CommentPost key={index} commentData={comment}/>)
                             }
                    </span>
                  </div>
             )
        })
    }
}

const rowStyle = {
    background: '#f4f4f4',
    padding: '10px',
    border: '1px #ccc dotted',
    margin: '14px'
}
export default DisplayPosts;
```

#### Step 17 :
To host the project in AWS S3:
```
amplify hosting add
```

---
## 🎉 That's It. Cheers 🎉