Part 3: AppSync Frontend: AWS Managed GraphQL Service AWS AppSync architecture : : : AppSync Frontend: AWS Managed GraphQL Service (this post) : (Latest!!!) Part 1 Introduction: GraphQL endpoints with API Gateway + AWS Lambda Part 2 AppSync Backend: AWS Managed GraphQL Service Part 3 Part 4 Serverless AppSync Plugin: New Features “ ” — Part 2 AWS AppSync is a fully managed Serverless GraphQL service for real-time data queries, synchronization, communications and offline programming features. Introduction In this post, we will learn about building mini Twitter App’s client components using and . In particular, I will focus on : ReactJS AWS AppSync . User Authentication with AWS Amplify . Mini Twitter App Components . GraphQL Subscriptions GraphQL Mutations with Optimistic UI and Offline Support. . Serverless Client Deployment with Netlify and S3 Let’s get started! 🏃 In we have created mini Twitter App’s backend GraphQL API using AWS AppSync, DynamoDB, ElasticSearch and AWS Lambda. We also deployed the API using new . Note 1: Part 2 serverless-appsync-plugin You can quickly get started with this App in the repository using Please make sure configs are set properly. Note 2: serverless-graphql yarn start. AppSync Client also has SDK’s for native iOS, web, and React Native, with Android but in this post, we are going build a React JS App with . Note 3: JS SDK AppSync Client + AWS Amplify Client uses under the hood to simplify user authentication, manage offline logic and support subscriptions. AppSync Apollo Client 2.0 real-time On the other hand, you can use AppSync Client with to simplify user authentication workflows in your application 🔑. AppSync provides authentication using API Key, Cognito User Pools or AWS IAM policies and AWS Amplify complements the same with methods provided in for AWS Amplify Auth Class user sign-up, sign-in, password confirmation and sign-out. Amplify, { Auth } ; { } ; AWSAppSyncClient ; { ApolloProvider } ; import from 'aws-amplify' import withAuthenticator from 'aws-amplify-react/dist/Auth' import from 'aws-appsync' import from 'react-apollo' client = AWSAppSyncClient({ : 'https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql', : 'us-east-1', : { const new url region auth // AWS Cognito User Pool **type**: **AUTH\_TYPE**.**AMAZON\_COGNITO\_USER\_POOLS**, jwtToken: **async** () => (**await** Auth.currentSession()).getIdToken().getJwtToken(), // API KEY **type**: **AUTH\_TYPE**.**API\_KEY**, apiKey: 'xxxxxxxxxxxxx', // AWS IAM **type: AUTH\_TYPE.AWS\_IAM** credentials: () => Auth.currentCredentials(), },}); = () => (< >< {client}></ >); const WithProvider Router ApolloProvider client= Router ( ); export default withAuthenticator WithProvider As seen in code above, adding authentication to your application is as easy as wrapping your App’s main component with higher order component. You can use and packages by AWS for all of these integrations. App.js withAuthenticator _aws-appsync-react_ _aws-amplify-react_ For this demo, I am using AWS Cognito User Pool for User Authentication and have created two test users (sidg_sid and nikgraf) in Cognito. Once the user is logged in, their session is persisted in by Amplify. So the user can leave the page, come back, and still be logged in! You can find more details in by . Note: localStorage this post Nader Dabit User Authentication with AWS AppSync + AWS Amplify + AWS Cognito Mini Twitter App Components Now, the exciting stuff begins! 💃 The basic structure of this App is created using . Also, we are using to make our App look fancy 💅Given below are the main components of this application. [create-react-app](https://github.com/facebook/create-react-app) [styled-components](https://github.com/styled-components/styled-components) five : users can log in or sign out from this App UserLogin (previous section). : retrieve basic user profile info from DynamoDB. ProfileInfo : retrieve a list of tweets from ElasticSearch. ProfileTweets : users can send a tweet. TweetForm : users can search through a corpus of all tweets by keywords. TweetSearch In order to make it all work, we take advantage of specific GraphQL operations: — fetch profile info and list of tweets for a given user. Queries — create and delete a tweet for a given user. Mutations — followers of a given user can see his new tweets. Subscriptions Various Components of mini Twitter App Profile Info Component: In this section, you will see how to wire up this component using , from . ProfileInfoQuery graphql react-apollo The GraphQL schema for this App defines . The resolver for the query given below fetches data from DynamoDB for a given user getUserInfo handle. ProfileInfoQuery = gql**`query** ProfileInfoQuery( ; export const $handle: String!) {getUserInfo(handle: $handle) {namelocationdescriptionfollowing}}` The value of is parsed from JWT token and is available in or it can be provided as an input . The query above is resolved using the following mapping template in AppSync Backend. handle context.identity.username context.arguments.handle {"version" : "2017-02-28","operation" : " ","query" : {"expression": " = ","expressionValues" : {":handle" : {"S" : "${ }"}}}} Query handle :handle context.identity.username At the end, is: ProfileInfoComponent React ; { } ; { ProfileInfoQuery } ; import from 'react' import graphql from 'react-apollo' import from '../queries' = ({ data: { loading, }}) => { (loading) { ( < >Loading ...</ > ); } const ProfileInfo getUserInfo if return p p ( <d > < > { . } </ > </d > );}; return iv h4 getUserInfo name h4 iv (ProfileInfoQuery, {options: props => ({ : { : props. ,},}),})( ); export default graphql variables handle handle ProfileInfo : mini Twitter App’s GraphQL schema and resolvers are explained in . Note Part 2 Profile Tweets Component: Data for this component is also retrieved from defined in AppSync schema. The resolver for this query hits ElasticSearch tweet index and retrieves tweets by getUserInfo handle. ProfileTweetsQuery = gql**`query** ProfileTweetsQuery ; export const {getUserInfo {tweets(limit: 10) {items {tweettweet_id}nextToken}}}` Optimistic Response and Offline Support Now, lets imagine the following scenario: Scenario: You are coming back home after long day at work. You take the train from Station A to Station B. Now, you are also tweeting your thoughts on your favorite topic of interest but all of a sudden train goes through a tunnel, and now you are having network connectivity issues. How will your App handle this? In this possible scenario, a user would expect the App to behave normally (oh yeah! users expect a lot 😉and it doesn’t take them a lot of time to click on that delete App button 💁). This is where and come to the rescue when backend is unreachable. Optimistic Response Offline Support Our next component, handles the scenario explained above. In this case, puts a tweet record in ElasticSearch index. TweetForm create tweet mutation AddTweetMutation = gql**`mutation AddTweetMutation($tweet: String!) {createTweet(tweet: $tweet) {tweet_idtweet}}`**; export const Now, we need to add two more functionalities in our component, also explained in . this post defines the new response you would like to have available in the update function. optimisticResponse takes two arguments, the proxy (which allows you to read from the cache) and the data you would like to use to make the update. We read the current cache ( ), add it our new item to the array of items, and then write back to the cache, which updated our UI. update proxy.readQuery (AddTweetMutation, {props: ({ mutate }) => ({addTweet: tweet => { mutate({ : {tweet,},optimisticResponse: () => ({ : {tweet, : uuid(), : ,},}),update: (proxy, { data: { createTweet } }) => { data = proxy.readQuery({ : ProfileTweetsQuery, : {tweet,},});data. . . .push(createTweet);proxy.writeQuery({ : ProfileTweetsQuery,data, : {tweet,},});},});},}),})(TweetFormComponent); export default graphql return variables createTweet tweet_id __typename 'Tweet' const query variables meInfo tweets items query variables and.. boom! You can see the magic yourself 👓 Let’s see how all this real-time stuff works: The best part? All you need to get subscriptions working in the backend is to extend your GraphQL schema with 4 lines of code: type Subscription {addTweet: Tweet@aws_subscribe(mutations: [“createTweet”]} Scenario: Let’s say we have two users (sidg_sid and nikgraf) following each other. In this case, both the users are subscribed to each other’s tweets. As shown below, when user sidg_sid sends a tweet it is immediately pushed to all his followers including nikgraf and vice-versa. Real Time Subscriptions Subscriptions in AWS AppSync are invoked as a response to a mutation. In addition, they are handled automatically by the AWS AppSync client SDK using MQTT over Websockets as the network protocol between the client and service. The following subscription is invoked every time a new tweet is added. AddTweetSubscription = gql**`subscription AddTweetSubscription {addTweet {__typenametweet_idtweet}}`**; export const {AddTweetSubscription,}; export default We now add this subscription to by calling function with and user . The adds a new tweet in the list of previous tweets for a given user. Profile Tweets Component subscribeToMore AddTweetSubscription handle updateQuery tweetsQuery = (ProfileTweetsQuery, {options: props => ({ : { ...variables, : props. }, : ,}),props: props => ({...props,subscribeToNewTweets: params =>props. .subscribeToMore({ : AddTweetSubscription, : params,updateQuery: (prev, { subscriptionData: { data: { addTweet } } }) => { {...prev, : {...prev. , : { : [addTweet, ...prev. . . ],},},};},}),}),}); const graphql variables handle handle fetchPolicy 'cache-and-network' data document variables return getUserInfo getUserInfo tweets items getUserInfo tweets items compose(tweetsQuery)(ProfileTweetsComponent); export default Search all Tweets Component Last but not the least, users can also search through corpus of tweets by keyword. The resolver for this query maps to an ElasticSearch query in the backend. SearchTweetsQuery = gql**`query UserQuery($keyword: String!) {searchAllTweetsByKeyword(keyword: $keyword) {items {tweettweet_id}}}`**; export const ElasticSearch Query At the end, is: SearchTweetsComponent React ; { } ; { SearchTweetsQuery } ; import from 'react' import graphql from 'react-apollo' import from '../queries' Search = ({ data: { loading, searchAllTweetsByKeyword }}) => { (loading) { ( < >Loading ...</ > ); } const if return p p (< >{searchAllTweetsByKeyword. .map((item, index) => (< {index}>{item.tweet}</ >))}</ >);}; return Container items Tweet key= Tweet Container (SearchTweetsQuery, {options: props => ({ : { : props. ,},}),})( ); export default graphql variables handle handle Search Serverless Client Deployment with Netlify and/or S3 Deploy Netlify: **yarn build && netlify deploy build**Deploy S3: yarn build && serverless client deploy serverless-graphql-client service: **frameworkVersion: ">=1.21.0 <2.0.0" provider:name:** aws nodejs6.10 dev us-east-1 runtime: stage: region: - serverless-finch plugins: <unique bucket name> build custom:client:bucketName: distributionFolder: Special Thanks for working together to implement the client components. and for helping and reviewing the code. Nik Graf Manuel Nader Last but not the least, to everyone for encouraging me to write more and appreciating previous blogs. I would like to end this blog with one of my favourite quotes — Albert Einstein “Imagination is more important than knowledge. For knowledge is limited, whereas imagination embraces the entire world, stimulating progress, giving birth to evolution.” —