In this guide, we’ll build a full-stack todo application using AWS Amplify and React. The app will include user authentication, API integration, and real-time updates. By the end, you’ll have a production-ready application deployed on AWS.
We’ll build a todo app with the following features:
Node.js (v14.x or later)
brew install node
sudo apt install nodejs npm
AWS Account
Code Editor
# For all operating systems
npm install -g @aws-amplify/cli
# Verify installation
amplify --version
amplify configure
Follow the prompts:
npx create-react-app amplify-todo
cd amplify-todo
npm install @aws-amplify/ui-react aws-amplify @mui/material @mui/icons-material @emotion/react @emotion/styled
amplify init
# Enter the following:
? Enter a name for the project: amplifytodo
? Enter a name for the environment: dev
? Choose your default editor: (Your preferred editor)
? Choose the type of app that you're building: javascript
? What javascript framework are you using: react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: npm run build
? Start Command: npm start
amplify add auth
# Use default configuration:
? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings? No
// src/App.js
import { Amplify } from 'aws-amplify';
import { withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import awsExports from './aws-exports';
Amplify.configure(awsExports);
function App({ signOut, user }) {
return (
<div>
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</div>
);
}
export default withAuthenticator(App);
amplify add api
# Enter the following:
? Please select from one of the below mentioned services: GraphQL
? Provide API name: todoapiamplify
? Choose the default authorization type for the API: Amazon Cognito User Pool
? Do you want to configure advanced settings for the GraphQL API: No
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields
Replace the contents of amplify/backend/api/todoapiamplify/schema.graphql
:
type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
name: String!
description: String
isComplete: Boolean!
createdAt: AWSDateTime
}
// src/components/TodoList.js
import React, { useState, useEffect } from 'react';
import { API, graphqlOperation } from 'aws-amplify';
import { createTodo, deleteTodo, updateTodo } from '../graphql/mutations';
import { listTodos } from '../graphql/queries';
import { onCreateTodo, onDeleteTodo, onUpdateTodo } from '../graphql/subscriptions';
import {
List, ListItem, ListItemText, ListItemSecondaryAction,
IconButton, TextField, Button, Checkbox
} from '@mui/material';
import { Delete, Edit } from '@mui/icons-material';
function TodoList() {
const [todos, setTodos] = useState([]);
const [formData, setFormData] = useState({ name: '', description: '' });
useEffect(() => {
fetchTodos();
subscribeToNewTodos();
subscribeToDeletedTodos();
subscribeToUpdatedTodos();
}, []);
async function fetchTodos() {
try {
const todoData = await API.graphql(graphqlOperation(listTodos));
setTodos(todoData.data.listTodos.items);
} catch (err) {
console.log('error fetching todos:', err);
}
}
async function addTodo(event) {
event.preventDefault();
try {
await API.graphql(graphqlOperation(createTodo, {
input: {
name: formData.name,
description: formData.description,
isComplete: false
}
}));
setFormData({ name: '', description: '' });
} catch (err) {
console.log('error creating todo:', err);
}
}
async function toggleTodoComplete(todo) {
try {
await API.graphql(graphqlOperation(updateTodo, {
input: {
id: todo.id,
isComplete: !todo.isComplete
}
}));
} catch (err) {
console.log('error updating todo:', err);
}
}
async function removeTodo(id) {
try {
await API.graphql(graphqlOperation(deleteTodo, { input: { id } }));
} catch (err) {
console.log('error deleting todo:', err);
}
}
function subscribeToNewTodos() {
API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: (todoData) => {
const newTodo = todoData.value.data.onCreateTodo;
setTodos(prevTodos => [...prevTodos, newTodo]);
}
});
}
function subscribeToDeletedTodos() {
API.graphql(graphqlOperation(onDeleteTodo)).subscribe({
next: (todoData) => {
const deletedTodo = todoData.value.data.onDeleteTodo;
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== deletedTodo.id));
}
});
}
function subscribeToUpdatedTodos() {
API.graphql(graphqlOperation(onUpdateTodo)).subscribe({
next: (todoData) => {
const updatedTodo = todoData.value.data.onUpdateTodo;
setTodos(prevTodos => prevTodos.map(todo =>
todo.id === updatedTodo.id ? updatedTodo : todo
));
}
});
}
return (
<div>
<form onSubmit={addTodo}>
<TextField
label="Todo Name"
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
/>
<TextField
label="Description"
value={formData.description}
onChange={e => setFormData({ ...formData, description: e.target.value })}
/>
<Button type="submit" variant="contained" color="primary">
Add Todo
</Button>
</form>
<List>
{todos.map(todo => (
<ListItem key={todo.id}>
<Checkbox
checked={todo.isComplete}
onChange={() => toggleTodoComplete(todo)}
/>
<ListItemText
primary={todo.name}
secondary={todo.description}
/>
<ListItemSecondaryAction>
<IconButton edge="end" onClick={() => removeTodo(todo.id)}>
<Delete />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
);
}
export default TodoList;
// src/App.js
import { Amplify } from 'aws-amplify';
import { withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { Container, AppBar, Toolbar, Typography, Button } from '@mui/material';
import TodoList from './components/TodoList';
import awsExports from './aws-exports';
Amplify.configure(awsExports);
function App({ signOut, user }) {
return (
<div>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" style={{ flexGrow: 1 }}>
Todo App - Welcome {user.username}
</Typography>
<Button color="inherit" onClick={signOut}>
Sign Out
</Button>
</Toolbar>
</AppBar>
<Container maxWidth="md" style={{ marginTop: '2rem' }}>
<TodoList />
</Container>
</div>
);
}
export default withAuthenticator(App);
amplify push
# Confirm all the changes: Yes
# Generate code for API: Yes
amplify add hosting
# Select:
? Select the plugin module to execute: Hosting with Amplify Console
? Choose a type: Manual deployment
Then deploy:
amplify publish
# If you see GraphQL errors, try:
amplify api gql-compile
amplify push
# Reset authentication service:
amplify remove auth
amplify add auth
amplify push
# Clear build files and redeploy:
rm -rf node_modules
rm -rf build
npm install
amplify push
amplify publish
Add Features
Enhance Security
Improve Performance
Remember to clean up resources when you’re done experimenting:
amplify delete
This will remove all cloud resources associated with your project to avoid unexpected charges.
You’ve now built and deployed a full-stack application using AWS Amplify. This foundation can be extended to build more complex applications with additional features like file storage, analytics, or AI/ML capabilities.
The key concepts covered in this tutorial:
Keep exploring the AWS Amplify ecosystem to discover more features and capabilities for your next project!