Building a serverless TaskPlanner APP with AWS
Introduction
This is a revision of my Todolist app, which uses:
AWS lambda
DynamoDB
IAM
API Gateway
MUI
This project is the first time I build a serverless app on cloud and utilize a noSQL database into production. A doc that record the problems and features of the app might be helpful.
![[Pasted image 20230120223602.png]]
Basic AWS setup
Create Role
- Go into DynamoDB, create a table first
- copy the tabel ARN![[Pasted image 20230120203223.png]]
- Create Role ![[Pasted image 20230120203745.png]]
- On the next page, Add AWSLambdaBasicExecutionRole permission
- Create role
- Click into the role and select inline policy![[Pasted image 20230120204354.png]]
- Select DynamoDB for service, set up actions and put in the table ARN![[Pasted image 20230120204521.png]]
Functions (Lambda)
Creating functions are less complicated. Remember to use the execution role we just created ![[Pasted image 20230120205437.png]]
API
Create a REST API with Edge optimized endpoint![[Pasted image 20230120210531.png]]
Set up the method as above.
The POST method need to specify a model ![[Pasted image 20230120211023.png]]
![[Pasted image 20230120211144.png]]
Create New Task
set up appropriate useState variables to store values and store error state.
1 | <TextField |
When clicking submit button, error check is performed
1 | if (title == "") { |
Posting data
There’s not too much worth mentioning on the frontend side. One thing when we want to find the difference in two dates in days, use:
1 | (differenceInCalendarDays( |
In this way, two same date value will have a difference of 0.
where end and startValue is set using Datepicker
1 | <DatePicker |
![[Pasted image 20230121205321.png]]
This can put the date into readable format, and mm/dd/yyyy and be passed into Date() as well.
AWS putItem
1 | const crypto = require('node:crypto'); |
DynamoDB is noSQL so we will need a sort key to separate different items, in this case, set tasks’ SK to “task”. PK is basically an ID, hence the above UUID method would work.
https://stackoverflow.com/questions/11721308/how-to-make-a-uuid-in-dynamodb
View name and details of the task
1 | const params={ |
Use FilterExpression:"contains(SK,:sk)",
and documentClient.scan(params)
to get all objects with specific condition (eg. sort key)
Fetch items as usual, we can put a setLoading(false);
when we recieved the response to achieve a progress bar function
Sort
In this case, we also want to sort items by some value.
response.data.forEach((note) => sortByTime(note));
1 | const sortByTime = (note) => { |
setTheArray(oldArray => [...oldArray, newElement])
can set a new array in useState hook without “appending” new object
then
1 | <Grid container> |
make a note of the {loading ? <CircularProgress /> : <></>}
to achieve progress bar function.
The child component will then render the task one by one.
View progress
The thing worth mentioning here is the calculation of current date and end date.(differenceInCalendarDays(new Date(note.endDate), new Date()) + 1);
react-bootstrap progress bar:
1 | <ProgressBar |
to calculate progress today:
1 | const progressToday = |
This value will be added to the current progress and pushed to database when updating.
Finish task
1 | <CompleteButton |
From the AWS end
1 | let {currentProgress, lastFinishDate, id,taskName,taskDetail,startDate, endDate,totalHours, timeSection,quantity,quantifier} = JSON.parse(event.body) |
The updated attributes must all be there.
Or just update single attribute, but some helper method is needed, havent tried yet.:
https://stackoverflow.com/questions/55825544/how-to-dynamically-update-an-attribute-in-a-dynamodb-item
Deleting task
1 | <IconButton> |
The setNotes doesn’t seem to be working
The AlertDialog component:
1 | return ( |
Make a note on how to pass in the function <AlertDialog action={() => handleDelete(note.PK)}></AlertDialog>
and <Button onClick={()=>{action();setOpen(false)}}>Yes</Button>
Milestones
Create
milestones are separated from the task object which are added in the TaskDetail pop up page.
1 | <Button |
.then(() => {navigate("/");setOpen(false);setDisabled(false);setMilestone([...milestone, {"milestoneName":details, "deadline":startValue}])})
The setMilestone
function and milestone
is passed from te parent component to achieve a real time update effect.
The lambda function uses milestone+name of the milestone as the SK (this is not safe), PK as the taskID.
1 | Item:{ |
Get
1 | const {id} = event.pathParameters |
fetch milestones of one task at a time, pass in the task ID and narrow the result down to milestone objects by using begins_with(SK, :sk)
which :sk
is "milestone"
,return as a list
Display
1 | function closestDate(d) { |
use milestone.filter(closestDate)[0].milestoneName
to get the lowest date difference between deadline date and current date (if not null) and display on task card.
1 | function custom_sort(a, b) { |
Sort by date
TaskDetail
1 | <IconButton> |
1 | <IconButton> |
👆is the structure of creating pop up dialogue.
Editing
<EditIcon onClick={()=>{setEdit(!edit)}}></EditIcon>
sets the edit state{edit ? <></>:<></>}
decides what to display based on edit state.
Bugs
Module not found: Error: Can’t resolve ‘date-fns/addDays’
Solution:
DatePicker requiresdate-fns
package from NPM using npm install --save date-fns
.
How do I include a file over 2 directories back?
https://stackoverflow.com/questions/162873/how-do-i-include-a-file-over-2-directories-back
Solution:
..
selects the parent directory from the current. Of course, this can be chained:../../index.php
This would be two directories up.
npm install goes to dead in China
https://stackoverflow.com/questions/22764407/npm-install-goes-to-dead-in-china
Solution:
There is a Chinese registry now too:
1 | $ npm config set registry http://r.cnpmjs.org |
React Hooks: useEffect() is called twice even if an empty array is used as an argument
solution
React.Strict mode is on
StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).
Remove Json object from json array element (eg delete task from list of task and display dynamically)
solution: not tested https://stackoverflow.com/questions/48163429/remove-json-object-from-json-array-element
passing props
You are passing the props in a wrong way. Either use it as a single object in props or have all the props it inside {} using destructuring method.
1 | export default function List({email, phone, nick}) {} |
Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop
Solution:
use
1 | const [open, setSnackBarState] = useState(variant ? true : false); |
instead of:
1 | const [open, setSnackBarState] = useState(false); |