Serverless GraphQL React App using AWS Amplify — Part One

James Hamann
5 min readNov 26, 2018

Previously I’ve posted about creating a Serverless React Web App using AWS Amplify. After playing around with AppSync in Amplify and enjoying the results, I thought it’d be useful to start a series of posts on how to setup a simple Serverless GraphQL React App using Amplify.

If you want to understand more about GraphQL, you can read my previous post on it here.

I’ll be keeping things pretty generic and going with the same old recipe of a simple inventory app, where we can Create, Read, Update and Delete (CRUD) items.

Getting Started

First things first, you’re going to need to make sure you have completed the below before starting:

Now we’re ready to kick on!

#bash
$ yarn create react-app my-app[...]
✨ Done in 45.41s.
$ cd my-app$ yarn start

If everything was installed and setup correctly, you’ll see the default react page.

Great, now let’s get Amplify setup. The CLI tool makes it so easy, all you need to do is follow the prompts and everything else will fall into place!

#bash$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project serverlessGraphqlApp
? Choose your default editor: Atom Editor
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: yarn build
? Start Command: yarn start
Using default provider awscloudformation
[...]
✔ Successfully created initial AWS cloud resources for deployments.Your project has been successfully initialized and connected to the cloud!

You’ll notice this creates an amplify folder which contains two other folders, one will resemble your local backend and the other will replicate what’s currently live in the cloud. As we continue building our app, keep an eye on the folder, it’ll provide a good snapshot of what back-end resources we’re using.

That’s the basic setup complete, next we’ll scaffold our front-end together.

Front-End Setup

In order to keep things simpler for the purpose of this post, I’ve chosen to use the React UI framework, Material-UI. This will help us scaffold our app’s components together quite quickly and provide a nice, clean UI to work with. Feel free to use any UI framework you like, though.

Let’s start by installing material-ui, making some sub folders for our screens and components and creating two new files: home.js and appNavBar.js in their respective directories.

#bash$ yarn add @material-ui/core
[...]
$ yarn add @material-ui/icons
[...]
$ mkdir src/screens
$ mkdir src/components
$ touch src/screens/home.js
$ touch src/components/appNavBar.js

Using the examples over at material-ui, our appNavBar.js file should look like the below.

#src/components/appNavBar.jsimport React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
const styles = theme => ({
button: {
margin: theme.spacing.unit,
float: 'right',
},
root: {
flexGrow: 1
},
grow: {
flexGrow: 1,
},
});
class AppNavBar extends Component {render(){
const { classes } = this.props;
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow}>
Items
</Typography>
</Toolbar>
</AppBar>
</div>
);
}
}
AppNavBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(AppNavBar);

Next, we’ll import our AppNavBar component into our newly created home.js file.

#src/screens/home.jsimport React, { Component } from 'react';
import AppNavBar from '../components/appNavBar'
class Home extends Component {
render() {
return (
<AppNavBar />
);
}
}
export default Home;

Now, let’s import our Home screen into our main App.js file.

#src/App.jsimport React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './screens/home'
class App extends Component {
render() {
return (
<Home />
);
}
}
export default App;

Finally, let’s add a dialog form that will serve as our AddItem form.

Using Material UIs Dialog component, we’ll create a form with fields for an item’s name, price, description and rating.

#bash $ touch src/components/addItem.js--------------------------------------------------------------------
#addItem.js
import React, { Component } from 'react';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import AddIcon from '@material-ui/icons/Add';
import Button from '@material-ui/core/Button';
class AddItem extends Component {state = {
open: false,
};
handleClickOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
render() {
return (
<div style={{display: 'flex', flexWrap: 'wrap'}}>
<Button variant="fab" mini color="inherit" aria-label="Add" onClick={this.handleClickOpen}>
<AddIcon />
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Add a New Item</DialogTitle>
<DialogContent>
<DialogContentText>
</DialogContentText>
<TextField
style={{marginRight: 10}}
id="beerName"
label="Name"
type="string"
onChange={this.handleChange('itemName')}
/>
<TextField
style={{marginRight: 10}}
id="beerABV"
label="ABV%"
type="number"
onChange={this.handleChange('itemPrice')}
/>
<TextField
style={{marginRight: 10}}
id="beerRating"
label="Rating"
type="number"
onChange={this.handleChange('itemRating')}
/>
<TextField
style={{marginTop: 10}}
multiline
id="beerDescription"
label="Description"
type="string"
rows="4"
fullWidth
onChange={this.handleChange('itemDescription')}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleSubmit} color="primary">
Add Item
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
export default AddItem;

Looking a little closer, the functions handleClickOpen and handleClose are pretty self explanatory. The function handleChange also does exactly what it says, it handles the change of each text field value. To keep a track of each attribute’s value, we pass it’s name as the argument. Using this method, we can add as many fields as we like, as long as we pass the name of that field as an argument when calling the function.

Lastly, let’s make sure to import our AddItem component in our AppNavBar.

#src/components/appBar.js[...]
import AddItem from './addItem'
class AppNavBar extends Component {render(){
const { classes } = this.props;
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow}>
Items
</Typography>
<AddItem />
</Toolbar>
</AppBar>
</div>
);
}
}
AppNavBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(AppNavBar);

If all is well, our groundbreaking app should look like this…

Awesome, great work!

In Part 2 we’ll be looking at implementing the simple CRUD functions as well as further developing the layout and UI of our app.

All source code is available here.

As always, thanks for reading, hit 👏 if you like what you read and be sure to follow to keep up to date with future posts.

--

--