Complete Backend Setup with GraphQL and AWS AppSync, Amplify with React Frontend

AWS AppSync, Amplify, DynamoDB, S3 and React, GraphQL

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

Step 2 :

To create a React App:

npx create-react-app AppName

Step 3 :

To start the react dev server on localhost:

npm start

Step 4 :

To initialize the AWS Amplify within the project:

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:

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:

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:

amplify console api

Step 8 :

To install AWS modules for working with react:

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:

import Amplify from 'aws-amplify'
import aws_exports from './aws-exports'
Amplify.configure(aws_exports)

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

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

Step 12 :

To add Authentication using Amazon Cognito:

amplify add auth

Prompts:

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

Step 14 :

in the App.js file:

import { withAuthenticator } from 'aws-amplify-react'export default withAuthenticator(App, { includeGreetings: true });

Step 15 :

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

Step 16 :

A 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;

Step 17 :

To host the project in AWS S3:

amplify hosting add

🎉 That’s It. Cheers 🎉

I am a programmer and developer. I have worked and contributed many personal and Open-Sourced projects.