Typesafe, Fullstack React & GraphQL with AWS Amplify
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
At this point we have our simple app with some mock data:
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
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!
}
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
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
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
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
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
}
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!
};
}
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.
I’m sure you have questions - let’s hear them!