Typesafe, Fullstack React & GraphQL with AWS Amplify

swyx - Sep 15 '20 - - Dev Community

This is the blog form of a talk I gave at React Summit and Reactathon 2020

Resources

I am going to skim over the slides, which are available here.

GitHub Repo for this demo: https://github.com/sw-yx/talk-typesafe-fullstack-react-demo-cms

I will also assume you already have the AWS Amplify CLI setup and configured.

Livecode Demo Script

First we clone our premade React + TypeScript app and initialize it as an AWS Amplify project:

git clone https://github.com/sw-yx/talk-typesafe-fullstack-react-demo-cms
cd talk-typesafe-fullstack-react-demo-cms
yarn
amplify init # select defaults for everything
Enter fullscreen mode Exit fullscreen mode

At this point we have our simple app with some mock data:

https://user-images.githubusercontent.com/6764957/93261590-7c13f980-f7d5-11ea-9fda-b482ac1abdac.png

This gives us a strongly typed frontend with React and TypeScript! If you wish to learn how to wield React and TypeScript well, check out the React and TypeScript cheatsheet I have been maintaining for over two years!

Adding a GraphQL Database

We will now add a strongly typed backend to complement the frontend, using Amplify and AWS AppSync:

amplify add api

# choose the graphql option and defaults for the rest

? Please select from one of the below mentioned services: GraphQL
? Provide API name: myapiname
? Choose the default authorization type for the API API key
? Enter a description for the API key: 
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API No, I am do
ne.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID,
 name, description)

The following types do not have '@auth' enabled. Consider using @auth with @model
         - Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/directives#auth


GraphQL schema compiled successfully.

? Do you want to edit the schema now? Yes
Enter fullscreen mode Exit fullscreen mode

We'll use GraphQL SDL to define our database schema:

# amplify/backend/api/myapiname/schema.graphql
type Blog @model {
  id: ID!
  title: String!
  image: String!
  body: String!
}
Enter fullscreen mode Exit fullscreen mode

That @model there is a special GraphQL directive, which AppSync uses to provision infrastructure for you alongside your GraphQL model via a library called GraphQL Transform. It has loads more goodies you can explore on your own, like @auth, @searchable, @function and @predictions, that you can add to your backend.

Provisioning this infrastructure in AWS takes a long time, so we'll kick it off in the background while we work on the rest of the app:

amplify push -y # skips the yes check
Enter fullscreen mode Exit fullscreen mode

Wiring up Backend to Frontend

We're in the home stretch. We'll need the aws-amplify library for interacting from the frontend:

yarn add -D aws-amplify
Enter fullscreen mode Exit fullscreen mode

Notice that during amplify init an aws-exports.js file was generated in your src folder, with some non-secret information for your backend. We'll use this to wire up our app with AWS Amplify:

// // src/index.tsx

// the other imports
import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

// rest of the app
Enter fullscreen mode Exit fullscreen mode

You also notice that during amplify push there was an autogenerated folder in src/graphql with a bunch of GraphQL queries. We will use this in our app!

Optional step first - we can configure the codegen to generate typescript, so that types and auto imports work:

amplify codegen configure

? Choose the code generation language target typescript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/
graphql/**/*.ts
? Enter the file name for the generated code src/API.ts
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
Enter fullscreen mode Exit fullscreen mode

Then we will use the listBlogs query in our app!

// src/App.tsx
import { API } from 'aws-amplify'; // new
import { listBlogs } from "./graphql/queries"; // new
// other imports here

function App() {

  // new
  React.useEffect(fetchBlogs);
  function fetchBlogs() {
    const query = API.graphql({ query: listBlogs }) as Promise<any>
    query.then(
      ({
        data: {
          listBlogs: { items },
        },
      }) => setBlogs(items)
    );
  }
  // etc
}
Enter fullscreen mode Exit fullscreen mode

This sets up the blog items to refresh from backend whenever the app rerenders.

Then we'll also do the same for adding and updating blogs:

// make sure to import createBlog and updateBlog

  async function addBlog(values: Blog) {
    const timestamp = new Date();
    const newBlog: Blog = {
      ...values,
      id: uuidv4(),
      createdAt: timestamp,
      updatedAt: timestamp,
    };
    setBlogs([...blogs, newBlog]);
    await API.graphql({query: createBlog, variables: {input: values}}) // NEW!
  }
  function _updateBlog(oldValues: Blog) {
    return async function (newValues: Blog) {
      const timestamp = new Date();
      const newBlog: Blog = {
        ...newValues,
        createdAt: oldValues.createdAt,
        updatedAt: timestamp,
      };
      setBlogs([...blogs.filter((x) => x.id !== oldValues.id), newBlog]);

      const { createdAt, updatedAt, ...input } = newBlog; // NEW!
      await API.graphql({ query: updateBlog, variables: { input } }); // NEW!
    };
  }
Enter fullscreen mode Exit fullscreen mode

And there you have it! The basics of an end to end typed app!

You got stuck you can see the completed version of the app here https://github.com/sw-yx/talk-react-summit-demo-cms/blob/withAWS/src/App.tsx and the slides are here.

Alt Text

I'm sure you have questions - let's hear them!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .