How to Build Serverless Apps with React and AWS Amplify Tutorial
By Braincuber Team
Published on May 9, 2026
Building a full-stack application traditionally means provisioning servers, configuring databases, writing API code, and setting up authentication from scratch. Serverless computing eliminates these overheads by letting the cloud provider handle execution and resource allocation dynamically. This complete tutorial shows you how to build a fully functional serverless CRUD application with React using AWS Amplify. You will create a REST API backed by a DynamoDB NoSQL database, run Express.js code on Lambda functions, add authentication with Amazon Cognito, and wire it all together from the React frontend using a few lines of Amplify JavaScript library calls.
What You Will Learn:
- How to provision a complete serverless backend using a single Amplify CLI command
- How to create a DynamoDB NoSQL table with partition keys and global secondary indexes
- How AWS Lambda runs Express.js code behind API Gateway for REST endpoints
- How to modify generated Lambda code for custom CRUD operations on DynamoDB
- How to add Amazon Cognito authentication with user sign-up, sign-in, and access control
- How to call the REST API from React using the Amplify JavaScript library
- How to protect your entire React app with the withAuthenticator higher order component
Prerequisites
| Requirement | Details |
|---|---|
| Node.js and npm | Node.js 8+ installed locally with npm for package management |
| AWS Amplify CLI | Installed globally via npm install -g @aws-amplify/cli |
| Bootstraped React Project | A React app with Amplify library initialized, as covered in Part 1 setup |
| AWS Account | Active account with access to DynamoDB, Lambda, API Gateway, and Cognito |
| React Knowledge | Familiarity with React class components, state management, and event handling |
How AWS Amplify Provisions Serverless Services
The amplify add api command provisions four integrated AWS services with a single guided workflow. Behind the scenes, Amplify orchestrates CloudFormation templates that create these resources in the cloud when you run amplify push. The CLI asks a series of questions to determine your architecture, stores the specification locally, and then deploys everything together. Each resource is connected through IAM roles and policies that the CLI configures automatically.
Amazon DynamoDB
Fully managed NoSQL database. The amplify add api command creates a table with partition keys, optional sort keys, and global secondary indexes through a guided CLI wizard.
AWS Lambda
Serverless compute that executes code without provisioning servers. Generated code uses Express.js with the aws-serverless-express package to run RESTful APIs on Lambda behind API Gateway.
Amazon Cognito
User authentication and management service. When you choose to restrict API access, Amplify automatically adds the Auth category with user pools for sign-up, sign-in, and access control.
Amazon API Gateway
REST API endpoint management. Creates a proxy API with paths mapped to Lambda functions. When you restrict access, API Gateway validates Cognito tokens before forwarding requests to Lambda.
Step by Step: Creating the Serverless Backend with Amplify CLI
Open your terminal in the root directory of your React project and run amplify add api. The CLI presents an interactive questionnaire that walks you through every decision. Each answer determines how the four AWS services are configured and connected. Follow the exact sequence of prompts below to build a CRUD-ready serverless Todo API.
Run amplify add api and Select REST Service Type
Execute amplify add api. When prompted for a service type, select REST. Name the API category todosApi. Accept the default path /items for the REST endpoint. Choose Create a new Lambda function when asked for the Lambda source. Name the function resource todosLambda and the Lambda function itself todos. Select the pre-built CRUD function for Amazon DynamoDB table template for generated code.
Configure the DynamoDB Table with a Partition Key
Choose Create a new DynamoDB table when prompted for a data source. Name the resource todosTable and the table itself todos. Add a column named id with type String. Select id as the partition key when asked. Choose No for the sort key and No for global secondary indexes. Do not edit the local Lambda function when asked. This creates a simple key-value NoSQL table suitable for CRUD operations on todo items.
Add Authentication by Restricting API Access
Answer Yes when asked to restrict API access. Choose Authenticated and Guest users as the access group. Grant read/write access to authenticated users and read access to guest users. This triggers Amplify to add the Auth category with Amazon Cognito user pools for sign-up, sign-in, and token-based access control. Decline adding another path to finalize the configuration. Amplify will display Successfully added resource todosApi locally.
amplify add api
// Select: REST
// API Name: todosApi
// Path: /items
// Lambda: Create new - todosLambda / todos
// Template: CRUD function for DynamoDB
// DynamoDB: Create new - todosTable / todos
// Columns: id (String) - Partition Key
// Sort Key: No | GSI: No
// Edit function: No
// Restrict access: Yes
// Access: Authenticated and Guest - read/write, read
Locally Added Only - Not Yet in Cloud
Running amplify add api only defines resource specifications locally. The services are not provisioned in your AWS account yet. Run amplify status to see a table of all defined resources. The actual cloud provisioning happens when you run amplify push, which uploads templates to an S3 deployment bucket and calls the AWS CloudFormation API to create or update resources.
Modifying the Generated Lambda Code for Custom CRUD
Open the file amplify/backend/function/todosLambda/src/app.js. The CLI generated code uses Express.js with the aws-serverless-express middleware, which extracts the event object from API Gateway. The file contains pre-defined routes for CRUD operations on the DynamoDB table. Three specific changes are needed to make the API work correctly for the React frontend.
Fix the Table Name Variable and Update Route Definitions
Change the tableName variable from todosTable to todos. The CLI used the resource name instead of the actual table name. Add ProjectionExpression: "id, title" to the GET /items route's DynamoDB scan parameters to limit returned columns. Update the GET and DELETE route paths to include hashKeyPath + sortKeyPath so individual items can be retrieved and deleted by ID at paths like /items/:id.
app.get(path, function(req, res) {
const queryParams = {
TableName: tableName,
ProjectionExpression: "id, title"
};
dynamodb.scan(queryParams, (err, data) => {
if (err) {
res.json({ error: "Could not load items: " + err });
} else {
res.json(data.Items);
}
});
});
The two route path changes are critical for individual item access. Change line 77 to app.get(path + hashKeyPath + sortKeyPath, function(req, res) to enable GET by ID at /items/:id. Change line 173 to app.delete(path + hashKeyPath + sortKeyPath, function(req, res) to enable DELETE by ID at /items/:id. Both use the /items/:id URL pattern that the React frontend will call.
Provisioning Services to the Cloud with amplify push
The AWS resources have been defined and locally modified. To actually provision them in your AWS account, run amplify push. This command uploads the latest versions of the CloudFormation nested stack templates to an S3 deployment bucket, then calls the AWS CloudFormation API to create or update resources. Confirm the prompt with Yes. The push output includes a file src/aws-exports.js that contains configuration details for all provisioned services. This file is regenerated every time you run the push command and is consumed by the Amplify JavaScript library for React.
Amplify Services Architecture Summary
| AWS Service | Resource Name | Role in the Application |
|---|---|---|
| DynamoDB | todos (table) | NoSQL database storing todo items with id (partition key), title, and content columns |
| Lambda | todos (function) | Executes Express.js code to perform CRUD operations on the DynamoDB table |
| API Gateway | todosApi (REST) | Exposes REST endpoints at /items path mapped to the Lambda function |
| Cognito | Auth resource | User pools for sign-up, sign-in, access control. Guest users get read access; signed-in users get read/write. |
Building the React Frontend Components
The React frontend consists of three components: App (main component with form and routing logic), List (renders all todo items), and Details (shows a single item with delete capability). The Amplify JavaScript library provides the API client for making REST calls with auto-generated authentication headers. The withAuthenticator higher order component wraps the entire app with complete user registration, sign-in, and sign-out flows.
Configure Amplify, Import Components, and Add Bootstrap Styling
In src/App.js, import Amplify and the API client. Call Amplify.configure(aws_exports) to initialize with the generated config. Add Bootstrap CDN links in public/index.html. Create src/List.js to render the todo list as clickable list-group items. Create src/Details.js to display item content with Back to List and Delete buttons. Wire the delete button from Details to the App using a prop callback.
The App component manages three key state transitions. On mount, it calls fetchList() which invokes API.get("todosApi", "/items"). When adding a new item, handleSubmit() calls API.post("todosApi", "/items") with body containing id, title, and content. For item details, loadDetailsPage(id) calls API.get("todosApi", "/items/" + id). Notice you only need the apiName and path. The Amplify API client automatically adds the necessary authorization headers from the authenticated Cognito user session.
// Fetch all items
const response = await API.get("todosApi", "/items");
// Create a new item
await API.post("todosApi", "/items", {
body: { id: Date.now().toString(), title, content }
});
// Get a single item
const response = await API.get("todosApi", "/items/" + id);
// Delete an item (left as exercise)
await API.del("todosApi", "/items/" + id);
Adding Authentication with withAuthenticator
The Amplify React library provides the withAuthenticator higher order component that wraps any component with complete authentication flows. When you export export default withAuthenticator(App, true), the HOC automatically detects whether a user is signed in. If not, it displays sign-in and sign-up UI with Cognito integration. If signed in, the underlying App component renders. The second parameter set to true adds a sign-out button at the top of the page. Behind the scenes, the API.get(), API.post(), and API.del() methods automatically attach Cognito JWT tokens to every request, which API Gateway validates before forwarding to Lambda.
import Amplify, { API } from "aws-amplify";
import aws_exports from "./aws-exports";
import { withAuthenticator } from "aws-amplify-react";
Amplify.configure(aws_exports);
class App extends Component {
async componentDidMount() {
await this.fetchList();
}
async fetchList() {
const response = await API.get("todosApi", "/items");
this.setState({ list: [...response] });
}
handleSubmit = async event => {
event.preventDefault();
await API.post("todosApi", "/items", {
body: {
id: Date.now().toString(),
title: this.state.title,
content: this.state.content
}
});
this.setState({ content: "", title: "" });
this.fetchList();
};
loadDetailsPage = async id => {
const response = await API.get("todosApi", "/items/" + id);
this.setState({ item: { ...response }, showDetails: true });
};
}
// withAuthenticator(auto-detects auth state, second param = show sign-out)
export default withAuthenticator(App, true);
Left as Exercise: The Delete Function
The delete() function in App.js is intentionally left empty. Implement it by calling API.del("todosApi", "/items/" + id) followed by resetting showDetails to false and calling fetchList() to refresh. This exercise reinforces understanding of the Amplify API client pattern. You can also create a custom auth UI instead of withAuthenticator using Amplify Auth APIs from the documentation.
Frequently Asked Questions
What is the difference between amplify push and amplify publish?
amplify push provisions only the backend CloudFormation stacks to AWS. amplify publish additionally builds your frontend assets and deploys them to S3 with CloudFront hosting. For development, use amplify push to deploy backend changes while running the frontend locally with npm start.
How does the aws-serverless-express package work with Lambda?
The package bridges Express.js request/response objects with API Gateway Lambda events. It includes a middleware function that extracts the API Gateway event context and attaches it to req object. This allows your Lambda to run standard Express routes while being invoked through API Gateway proxy integration.
Can I use GraphQL instead of REST with AWS Amplify?
Yes, Amplify supports GraphQL APIs with AWS AppSync. Run amplify add api and choose GraphQL instead of REST. Amplify will provision AppSync with DynamoDB resolvers and optionally Cognito authentication. GraphQL offers real-time subscriptions which REST does not support out of the box.
Do I need to manually configure CORS for Amplify APIs?
No, CORS is enabled automatically by Amplify for API Gateway endpoints. When calling the API from your React frontend using the Amplify API client, authentication headers are added automatically and CORS preflight requests are handled. If calling from a different origin, configure CORS explicitly in API Gateway settings.
Why did the generated Lambda code need the table name fix?
The amplify add api CLI asks separately for the DynamoDB resource name and the table name. It incorrectly used the resource name todosTable as the tableName value in the generated Lambda code instead of the actual table name todos. This was a known bug that was likely fixed in later CLI versions. If your generated code already has the correct name, skip the fix.
Need Help Building Serverless Apps on AWS?
Our cloud experts can help you architect, develop, and deploy serverless applications using React, AWS Amplify, DynamoDB, Lambda, API Gateway, and Cognito with production-ready best practices.
