From Zero to 🚀: Building a Serverless Task Manager with React, Node.js, and AWS
TL;DR:
You’ll spin up a serverless to‑do app using React (frontend), AWS API Gateway + Lambda (backend), and DynamoDB (storage). Along the way, you’ll cover API design, simple auth, CI/CD with GitHub Actions, and infrastructure-as-code with the Serverless Framework. By the end, you’ll have a live demo, a polished README, and deploy badges for your GitHub profile.
Why You Should Try This
- Full‑stack spectrum: Frontend, backend, database, CI/CD and infra‑as‑code all in one project.
- Serverless is everywhere: From scrappy startups to big enterprise, “no‑ops” architectures are in demand.
- Resume‑worthy outcome: A live app you can demo, plus build/test/deploy badges for your README.
What You’ll Need
- Node.js & npm (v16+ recommended)
- AWS account with rights to deploy Lambda, API Gateway, DynamoDB
- Serverless Framework (or AWS SAM)—we’ll use Serverless here
- GitHub repo to host code + Actions workflows
- Basic React and JS skills
Table of Contents
- Project Kickoff & Architecture
- Scaffolding the React Frontend
- Creating the Serverless API
- Defining the Data Layer (DynamoDB)
- Adding Simple Auth
- CI/CD with GitHub Actions
- Infra as Code: serverless.yml
- Polishing Your README
- Next Steps & Ideas
1. Project Kickoff & Architecture
Before you write a single line of code, sketch out:
React App
↓ HTTPS
API Gateway
↓ Event JSON
Lambda (Node.js)
↓ AWS SDK
DynamoDB Table
-
Create a new GitHub repo called
serverless-task-manager
. - In your local folder, run:
git init
npx create-react-app frontend
mkdir backend
cd backend
npx serverless create --template aws-nodejs --path .
cd ..
git add .
git commit -m "Scaffold frontend + backend"
- Push to GitHub:
git remote add origin [email protected]:YOUR_USERNAME/serverless-task-manager.git
git push -u origin main
2. Scaffolding the React Frontend
Inside the frontend
folder:
- Install dependencies:
cd frontend
npm install axios tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
-
Configure Tailwind: in
tailwind.config.js
:
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
theme: { extend: {} },
plugins: [],
};
Add to src/index.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
- Build components:
-
TaskList.js
: fetches GET/tasks
-
TaskForm.js
: POST/tasks
-
TaskItem.js
: toggle complete → PUT/tasks/{id}
Example TaskForm.js
:
import { useState } from 'react';
import axios from 'axios';
function TaskForm({ onAdded }) {
const [text, setText] = useState('');
const submit = async e => {
e.preventDefault();
if (!text) return;
await axios.post(process.env.REACT_APP_API_URL + '/tasks', { text });
setText('');
onAdded();
};
return (
<form onSubmit={submit} className="flex gap-2">
<input
value={text}
onChange={e => setText(e.target.value)}
className="border p-2 flex-grow"
placeholder="New task..."
/>
<button type="submit" className="bg-blue-500 text-white px-4 rounded">
Add
</button>
</form>
);
}
export default TaskForm;
-
Environment variable: add
.env
infrontend
:
REACT_APP_API_URL=https://YOUR_API_GATEWAY.execute-api.us-east-1.amazonaws.com/dev
3. Creating the Serverless API
In the backend
folder:
- Install helpers:
cd backend
npm install aws-sdk uuid
-
Define handlers in
handler.js
:
const AWS = require('aws-sdk');
const { v4: uuid } = require('uuid');
const db = new AWS.DynamoDB.DocumentClient();
const TABLE = process.env.TASKS_TABLE;
module.exports.list = async () => {
const { Items } = await db.scan({ TableName: TABLE }).promise();
return { statusCode: 200, body: JSON.stringify(Items) };
};
module.exports.create = async event => {
const { text } = JSON.parse(event.body);
const item = { id: uuid(), text, done: false };
await db.put({ TableName: TABLE, Item: item }).promise();
return { statusCode: 201, body: JSON.stringify(item) };
};
module.exports.toggle = async event => {
const { id } = event.pathParameters;
const { done } = JSON.parse(event.body);
const updated = await db.update({
TableName: TABLE,
Key: { id },
UpdateExpression: 'set done = :d',
ExpressionAttributeValues: { ':d': done },
ReturnValues: 'ALL_NEW',
}).promise();
return { statusCode: 200, body: JSON.stringify(updated.Attributes) };
};
-
serverless.yml (we’ll expand infra‑as‑code later). For now, register three functions (
list
,create
,toggle
) under the/tasks
path.
4. Defining the Data Layer (DynamoDB)
- In
serverless.yml
, add:
provider:
name: aws
runtime: nodejs16.x
environment:
TASKS_TABLE: ${self:service}-tasks-${sls:stage}
resources:
Resources:
TasksTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-tasks-${sls:stage}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
- This creates a DynamoDB table keyed by
id
and lets your Lambdas read/write without provisioning throughput.
5. Adding Simple Auth
You have two main paths:
-
JWT: issue tokens on a
/login
endpoint (in-memory user map or Cognito). - Cognito: offload user pool + federated logins.
For brevity, you can stub a single “demo” user in handler.js
:
module.exports.login = async event => {
const { username, password } = JSON.parse(event.body);
if (username === 'demo' && password === 'demo') {
const token = Buffer.from('demo:demo').toString('base64');
return { statusCode: 200, body: JSON.stringify({ token }) };
}
return { statusCode: 401 };
};
Then in your React app, store that token in localStorage
and attach Authorization: Bearer ${token}
to your Axios calls. In serverless.yml
, add a simple API key or Lambda authorizer to enforce it.
6. CI/CD with GitHub Actions
Create .github/workflows/ci.yml
:
name: CI/CD
on:
push:
branches:
- main
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install & Test Frontend
working-directory: frontend
run: |
npm ci
npm test -- --watchAll=false
- name: Install & Test Backend
working-directory: backend
run: |
npm ci
npm test # if you write tests
deploy:
needs: build-and-test
runs-on: ubuntu-latest
environment:
name: production
steps:
- uses: actions/checkout@v3
- name: Deploy Serverless
working-directory: backend
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: npx serverless deploy --stage prod
After you add this, your README can show:
[](https://github.com/YOUR_USERNAME/serverless-task-manager/actions)
[](https://github.com/YOUR_USERNAME/serverless-task-manager/actions)
7. Infra as Code: serverless.yml
Here’s a more complete serverless.yml
to drop into your backend
:
service: serverless-task-manager
provider:
name: aws
runtime: nodejs16.x
region: us-east-1
environment:
TASKS_TABLE: ${self:service}-tasks-${sls:stage}
functions:
list:
handler: handler.list
events:
- http:
path: tasks
method: get
cors: true
create:
handler: handler.create
events:
- http:
path: tasks
method: post
cors: true
authorizer: NONE
toggle:
handler: handler.toggle
events:
- http:
path: tasks/{id}
method: put
cors: true
resources:
Resources:
TasksTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-tasks-${sls:stage}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
8. Polishing Your README
- Project title & description at top.
- Badges (build, test, deploy).
- Quick‑start:
git clone https://github.com/YOUR_USERNAME/serverless-task-manager.git
cd serverless-task-manager
cd frontend && npm ci && npm start
cd ../backend && npm ci && npx serverless deploy
- Screenshots or GIFs of your app in action.
- Live demo link (e.g. https://yourdomain.com).
- Next steps: invite contributions, list TODOs.
9. Next Steps & Ideas
- Add offline support (Service Workers + IndexedDB syncing).
- Swap DynamoDB for PostgreSQL on RDS (to show SQL chops).
- Integrate WebSockets via API Gateway for live updates.
- Build a mobile companion with React Native.
Top comments (0)