You’re going to build a complete data API backed by DynamoDB. By the end of this exercise, you’ll have a working endpoint that your frontend can call to create, list, and delete items, with data persisted in a DynamoDB table and served through the same Lambda and API Gateway infrastructure you set up earlier in the course.
This is the exercise where “static site” becomes “full-stack application.” It’s my favorite milestone in this whole course.
If you want AWS’s version of the table and query behavior open while you work, keep the aws dynamodb create-table command reference, the DynamoDB Query guide, and the DynamoDB Scan guide nearby.
Why It Matters
On Vercel or Netlify, you might use a hosted database like PlanetScale or Supabase and call it from a serverless function. On AWS, you’re building the equivalent—but you own every piece. The table, the function, the HTTP layer, the permissions. When something breaks, you know exactly where to look. When something costs money, you know exactly why.
Your Task
Build a data API that:
- Stores items in a DynamoDB table named
my-frontend-app-data - Supports GET (list items for a user), POST (create an item), and DELETE (remove an item)
- Runs as a Lambda function with the correct DynamoDB permissions
- Returns proper HTTP status codes and JSON responses
Use account ID 123456789012, region us-east-1, and the table/role names from the course conventions.
Create the DynamoDB Table
Create the my-frontend-app-data table with:
- A partition key named
userId(string) - A sort key named
itemId(string) - On-demand billing (
PAY_PER_REQUEST)
Use the CLI to create the table and wait for it to become active.
In the console, the Tables list shows the table status as Active once provisioning completes.

Checkpoint
Running aws dynamodb describe-table --table-name my-frontend-app-data --region us-east-1 --output json --query "Table.TableStatus" returns "ACTIVE".
Add DynamoDB Permissions to the Lambda Role
Your Lambda execution role (my-frontend-app-lambda-role) currently only has logging permissions. Create and attach a policy that grants:
dynamodb:GetItemdynamodb:PutItemdynamodb:DeleteItemdynamodb:Query
Scope the policy to the specific table ARN: arn:aws:dynamodb:us-east-1:123456789012:table/my-frontend-app-data.
Remember the principle of least privilege from Principle of Least Privilege—don’t grant dynamodb:* or use * as the resource.
Checkpoint
aws iam list-attached-role-policies --role-name my-frontend-app-lambda-role --region us-east-1 --output json shows both AWSLambdaBasicExecutionRole and your new DynamoDB policy.
Install the SDK and Update the Handler
Add the DynamoDB SDK packages to your Lambda project:
cd lambda
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbUpdate src/handler.ts to:
- Create a
DynamoDBDocumentClientat the top level (outside the handler function) - Read the table name from
process.env.TABLE_NAME, falling back tomy-frontend-app-data - Handle three HTTP methods:
- GET: If
itemIdis provided, return that single item (404 if not found). If noitemId, query all items for the givenuserId. - POST: Parse the request body for a
title, generate anitemId, and write the item to DynamoDB. Return the created item with a 201 status. - DELETE: Delete the item identified by
userIdanditemId. Return{ "deleted": true }.
- GET: If
- Return 400 for missing required parameters
- Return 405 for unsupported HTTP methods
- Catch errors and return 500 with a generic error message
Build the project and verify it compiles without errors.
Checkpoint
npm run build completes with no TypeScript errors. dist/handler.js exists.
Set the Environment Variable and Deploy
Set the TABLE_NAME environment variable on your Lambda function:
aws lambda update-function-configuration \
--function-name my-frontend-app-api \
--environment 'Variables={TABLE_NAME=my-frontend-app-data}' \
--region us-east-1 \
--output jsonPackage and deploy the updated handler code.
Checkpoint
aws lambda get-function-configuration --function-name my-frontend-app-api --region us-east-1 --output json --query "Environment" shows TABLE_NAME set to my-frontend-app-data.
Test Creating an Item
Invoke the function with a POST event:
{
"requestContext": {
"http": {
"method": "POST",
"path": "/"
}
},
"queryStringParameters": {
"userId": "user-123"
},
"body": "{\"title\": \"Learn DynamoDB\"}"
}Save this as test-create.json and invoke the function. Check the response.
Checkpoint
The response has statusCode: 201 and the body includes userId, itemId, title, status, and createdAt.
Test Listing Items
Create a second item with a different title (use the same userId), then invoke with a GET event that only includes userId—no itemId:
{
"requestContext": {
"http": {
"method": "GET",
"path": "/"
}
},
"queryStringParameters": {
"userId": "user-123"
}
}Checkpoint
The response has statusCode: 200 and the body includes an items array with both items you created.
Test Deleting an Item
Use the itemId from one of the items you created. Invoke with a DELETE event:
{
"requestContext": {
"http": {
"method": "DELETE",
"path": "/"
}
},
"queryStringParameters": {
"userId": "user-123",
"itemId": "item-1234567890"
}
}Replace item-1234567890 with the actual itemId from one of your POST responses. Then list items again with a GET to confirm the item is gone.
Checkpoint
The DELETE response has statusCode: 200 and { "deleted": true }. A subsequent GET for the same user shows only one item.
Test Error Cases
Invoke the function with a missing userId parameter and confirm you get a 400 response. Invoke with an unsupported method (like PUT) and confirm you get a 405.
Checkpoint
Missing userId returns statusCode: 400. Unsupported method returns statusCode: 405.
Checkpoints Summary
- DynamoDB table
my-frontend-app-dataexists and isACTIVE - Lambda role has both
AWSLambdaBasicExecutionRoleand a DynamoDB policy attached - Handler compiles without errors
-
TABLE_NAMEenvironment variable is set on the Lambda function - POST creates an item and returns 201
- GET lists all items for a user
- DELETE removes an item
- Missing parameters return 400
- Unsupported methods return 405
Failure Diagnosis
- The Lambda invocation fails with
AccessDeniedException: The execution role is missing DynamoDB permissions, or the policy points at the wrong table ARN. - You get validation errors or empty reads even after a successful write: The
TABLE_NAMEenvironment variable or the partition/sort key names in your code do not match the actual table schema. - DELETE succeeds but the item still appears on the next GET: You reused the wrong
itemId, or the delete request did not include the sameuserIdpartition key as the original item.
Stretch Goals
Wire it through API Gateway. If you completed the API Gateway exercise, connect your function to an HTTP API route and call it from
curlor the browser instead of usingaws lambda invoke.Add an update endpoint. Handle PATCH requests that update the
statusfield of an existing item usingUpdateCommandwith anUpdateExpression. Return the updated item withReturnValues: 'ALL_NEW'.Add pagination. Modify the GET handler to accept a
limitquery parameter and return anextCursorvalue. The client can pass the cursor back to get the next page of results.
When you’re ready, check your work against the Solution: Build a Lambda-Backed Data API with DynamoDB.