DEV Community

Cover image for AWS AppSync and Amplify with React & GraphQL - Complete Steps
Ayon Roy
Ayon Roy

Posted on

AWS AppSync and Amplify with React & GraphQL - Complete Steps

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:

npm install -g @aws-amplify/cli
Enter fullscreen mode Exit fullscreen mode

Step 2 :

To create a React App:

npx create-react-app AppName
Enter fullscreen mode Exit fullscreen mode

Step 3 :

To start the react dev server on localhost:

npm start
Enter fullscreen mode Exit fullscreen mode

Step 4 :

To initialize the AWS Amplify within the project:

amplify init
Enter fullscreen mode Exit fullscreen mode

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:

amplify add api
Enter fullscreen mode Exit fullscreen mode

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:

amplify push
Enter fullscreen mode Exit fullscreen mode

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:

amplify console api
Enter fullscreen mode Exit fullscreen mode

Step 8 :

To install AWS modules for working with react:

npm install aws-amplify aws-amplify-react
Enter fullscreen mode Exit fullscreen mode

Step 9 :

To configure AWS in the react app:
In the index.js file in the src folder of React App:

import Amplify from 'aws-amplify'
import aws_exports from './aws-exports'

Amplify.configure(aws_exports)
Enter fullscreen mode Exit fullscreen mode

Usage

Step 10 :

To use GraphQL queries within react:

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)
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 11 :

To add Subscriptions to automatically listen to changes:

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)
    }
Enter fullscreen mode Exit fullscreen mode

Step 12 :

To add Authentication using Amazon Cognito:

amplify add auth
Enter fullscreen mode Exit fullscreen mode

Prompts:

  • Default configuration
  • username/email
  • No, I am done #### Step 13 :
amplify push
Enter fullscreen mode Exit fullscreen mode

Step 14 :

in the App.js file:

import { withAuthenticator } from 'aws-amplify-react'

export default withAuthenticator(App, { includeGreetings: true });
Enter fullscreen mode Exit fullscreen mode

Step 15 :

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

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

Step 16 :

Complete example of CRUD of a blogpost with like, comment, edit, delete:

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;
Enter fullscreen mode Exit fullscreen mode

Step 17 :

To host the project in AWS S3:

amplify hosting add
Enter fullscreen mode Exit fullscreen mode

🎉 That's It. Cheers 🎉

Top comments (2)

Collapse
 
hectormunachi profile image
Hector Munachi

{ post.comments.items.length > 0 &&
Comments:
}
{
post.comments.items.map((comment, index) => )
}

Getting error from this: Uncaught (in promise) TypeError: Cannot read property 'length' of undefined

Collapse
 
hectormunachi profile image
Hector Munachi

Getting error from this: Uncaught (in promise) TypeError: Cannot read property 'length' of undefined

from the DisplayPosts.js