Serverless CRUD Microservice on AWS
Build a REST API with API Gateway, Lambda, and DynamoDB for full CRUD.
Introduction
30 Second Summary
Every time you check a product's price, add something to a cart, or remove an item from a wishlist, an API handles that request behind the scenes. Most of these APIs run without any servers to manage.
In this project, you will build a fully serverless product inventory API on AWS. You'll create, read, update, and delete items through a live endpoint that anyone can hit from anywhere.
What You'll Build
You'll run a curl command in your terminal, hit your live API URL, and watch product items flow in and out of a DynamoDB table in real time.
By the end of this project, you'll have:
- A live invoke URL you can hit from any machine to create, retrieve, update, and delete product items stored in a serverless database.
- A Python Lambda function that routes requests automatically by HTTP method and resource path, returning proper status codes for every outcome.
- A REST API with four working routes wired through API Gateway that you can demo to anyone with a single terminal command.
- Secret Mission: Lock down your API with key-based authentication and a usage plan that rate-limits traffic.
Are there any prerequisites?
You'll need an AWS Free Tier account, the AWS CLI installed and configured on macOS, and basic familiarity with Python and IAM.
Before We Start
Before diving in, let's lock in what you're about to build and why it matters to you. This step is your moment to commit to the goal before any hands-on work begins.
Set Up IAM Permissions
AWS follows a least-privilege security model. Every service starts with zero permissions and can only access what you explicitly allow.
Your Lambda function will need to read and write items in DynamoDB and send logs to CloudWatch. In this step, you'll create an IAM policy that grants exactly those permissions, then attach it to an execution role your Lambda can assume.
In this step, get ready to:
- Confirm your AWS CLI can reach your account.
- Create a custom IAM policy granting DynamoDB and CloudWatch Logs access.
- Create an execution role that Lambda can assume, with your policy attached.
Confirm your AWS CLI connection
Before creating resources in the console, confirm your terminal can authenticate with AWS. This ensures you can verify resources from the command line later.
- Confirm your CLI is connected to your AWS account by running this command:
aws sts get-caller-identity
You should see a JSON response containing your account ID, user ARN, and user ID.
Seeing an error instead?
If you see a credentials error, your AWS CLI may not be configured. Run aws configure and enter your Access Key ID, Secret Access Key, and default region.
If you see "command not found", your AWS CLI is not installed. Follow the official AWS CLI installation guide for macOS before continuing.
help me fix my AWS CLI connection
Create the IAM policy
This policy defines what your Lambda function is allowed to do. It grants six DynamoDB actions for CRUD operations and three CloudWatch Logs actions so Lambda can write execution logs.
- Open the AWS Management Console in your browser and sign in to your account.
- In the top search bar, type IAM and select the IAM service.
- In the left navigation pane, choose Policies.
- Click Create policy.
- In the Policy editor section, choose the JSON option.
- Delete the existing placeholder text, then paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
What does this policy do?
The first statement grants six DynamoDB actions: DeleteItem, GetItem, PutItem, Query, Scan, and UpdateItem. These cover the full range of CRUD operations your microservice needs.
The second statement grants three CloudWatch Logs actions so Lambda can create log groups, log streams, and write log events. Without these, you'd have no visibility into what your function does at runtime.
- Click Next.
- On the Review and create page, enter lambda-apigateway-policy in the Policy name field.
- Click Create policy.
You should see a green banner confirming the policy was created successfully.
Seeing a validation error?
Check that the JSON is valid. A common mistake is a missing comma between array items or a trailing comma after the last item.
Make sure you selected the JSON option in the Policy editor, not the Visual editor.
help me fix my IAM policy JSON
Create the IAM execution role
A policy defines permissions, but it needs to be attached to a role before any service can use it. An execution role is a role that Lambda assumes when your function runs.
- In the left navigation pane of the IAM console, choose Roles.
- Click Create role.
- Under Trusted entity type, select AWS service.
- Under Use case, select Lambda.
- Click Next.
- In the search bar on the permissions page, type lambda-apigateway-policy.
- Select the checkbox next to lambda-apigateway-policy.
- Click Next.
Why select Lambda as the use case?
This sets the trust policy for the role. It tells AWS that the Lambda service (lambda.amazonaws.com) is allowed to assume this role. Without this trust relationship, Lambda cannot use the permissions you attached.
- In the Role name field, enter lambda-apigateway-role.
- Click Create role.
You should see a green banner confirming the role was created.
Before you verify from the terminal, what do you think the output will contain? Think about what information describes an IAM role.
- Verify the role exists by running this command in your terminal:
aws iam get-role --role-name lambda-apigateway-role
You should see JSON output showing your role details, including the RoleName as lambda-apigateway-role and the AssumeRolePolicyDocument listing lambda.amazonaws.com as the trusted service.
Role not found?
Double-check the role name is exactly lambda-apigateway-role with no extra spaces or typos. Role names are case-sensitive.
If your CLI region doesn't match the console region, you may see errors. IAM is a global service, so this usually isn't the issue, but confirm your CLI credentials are valid.
help me troubleshoot my IAM role creation
Your IAM foundation is in place. Next up, you'll create the DynamoDB table and Lambda function that will use this role to perform real work.
Create the Lambda Function and DynamoDB Table
Your IAM permissions are locked in. Now you need the two core resources that power your microservice: a database to store product items, and a compute function to process requests.
In this step, you will create a DynamoDB table to hold your inventory data and a Lambda function that echoes back whatever it receives. Starting with a simple echo handler lets you confirm the function runs correctly before adding database logic in later steps.
In this step, get ready to:
- Create a DynamoDB table with a partition key for product IDs.
- Create a Lambda function with your custom execution role.
- Test the function and confirm it echoes back your test payload.
Create the DynamoDB table
Your microservice needs somewhere to store product items. A DynamoDB table with a simple partition key gives you a flexible, serverless data store that scales automatically.
- In the AWS Management Console, search for DynamoDB and open the DynamoDB console.
- In the left navigation pane, select Tables.
- Click Create table.
- For Table name, enter lambda-apigateway.
- For Partition key, enter id and leave the type set to String.
- For Table settings, keep the default selection of Default settings.
- Click Create table.
Wait until the table status changes to Active. This usually takes a few seconds.
What is a partition key?
A partition key uniquely identifies each item in a DynamoDB table. When you store a product, you provide an id value, and DynamoDB uses it to determine where the item lives internally. Every read, update, or delete targets a specific id.
Table stuck in Creating status?
Refresh the page after 10 seconds. If the table still shows Creating, check that your AWS Region matches the region where you created your IAM role.
help me figure out why my DynamoDB table is stuck in Creating status
Create the Lambda function
Now you need the compute layer. You will create a Lambda function that uses the execution role from Step 1, so it already has permission to talk to DynamoDB and write logs.
- In the AWS Management Console, search for Lambda and open the Lambda console.
- Click Create function.
- Select Author from scratch.
- For Function name, enter LambdaFunctionOverHttps.
- For Runtime, select Python 3.13.
- Expand Change default execution role.
- Select Use an existing role.
- In the Existing role dropdown, choose lambda-apigateway-role.
- Click Create function.
Lambda creates the function with a default source file named lambda_function.py. You will now replace that default code with a simple echo handler.
- In the Code tab, open lambda_function.py in the built-in code editor.
- Select all the existing code and replace it with the following echo handler:
import json
def lambda_handler(event, context):
# Return the received event back to the caller
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'message': 'Echo', 'event': event})
}
What does this code do?
- lambda_handler is the entry point Lambda calls when the function is invoked. It receives an event (the incoming request data) and a context object (runtime metadata).
- The function returns a response dictionary with statusCode, headers, and a JSON body. This structure is the format API Gateway expects from a Lambda proxy integration.
- By echoing the event back, you can confirm the function receives data correctly before adding database logic.
- Click Deploy to save your code changes.
You should see a green banner confirming the function was updated successfully.
Don't see the Deploy button?
Make sure you are on the Code tab (not the Test or Configuration tab). The Deploy button appears in the code editor section.
help me find the Deploy button in the Lambda code editor
Test the function
Your echo handler is deployed. Before moving on, you need to confirm it actually runs and returns the expected response.
- Click the Test tab in the Lambda console.
- For Event name, enter echoTest.
- Replace the default event JSON with the following test payload:
{
"httpMethod": "PUT",
"resource": "/items",
"body": "{\"id\": \"001\", \"name\": \"Widget\"}"
}
- Click Save to save the test event.
Before you run the test, what do you expect to see in the response? Think about what your echo handler does with the event it receives.
- Click Test to invoke the function.
You should see a green Execution result: succeeded banner. Expand the details to see the response body.
What should I see?
The response should show statusCode of 200 and a body containing your test event echoed back. The body will include the httpMethod, resource, and body fields you sent in. This confirms your function receives data and returns it correctly.
Seeing an error instead of a 200 response?
Check that your code matches the echo handler exactly. A missing comma, wrong indentation, or a typo in json.dumps can cause a syntax error.
Make sure you clicked Deploy after pasting the code. If you only saved the test event without deploying, Lambda is still running the old default code.
help me debug why my Lambda echo handler is returning an error
✔️ Awesome, I've got everything!
Great. Make sure you have saved and deployed your function code.
ⓧ I'd like to double check the full code
Here is the complete lambda_function.py at this point:
import json
def lambda_handler(event, context):
# Return the received event back to the caller
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'message': 'Echo', 'event': event})
}
Your data store and compute layer are both live and verified. Next up, you will wire them together behind a public REST API so the outside world can reach your Lambda function.
Create the REST API and Deploy
Your Lambda function can echo requests and your DynamoDB table is ready to store items. Right now, though, nothing can reach them from the outside world.
In this step, you will create a public HTTPS endpoint using API Gateway. This gives your Lambda function a live URL that anyone can send requests to. You will wire up a PUT route, update the Lambda code to write items to DynamoDB, and deploy the whole thing to a stage so it is accessible from your terminal.
In this step, get ready to:
- Create a REST API with a PUT method wired to your Lambda function.
- Update the Lambda code to store items in DynamoDB.
- Deploy the API and test it with a live curl request.
Create the REST API and wire up a PUT method
A REST API in API Gateway acts as the front door to your backend. You will create the API, add a resource path, and connect a PUT method to your Lambda function using Lambda proxy integration.
- In the AWS Console, search for API Gateway and open the service.
- Click Create API.
- Find the REST API card (not the one labelled "Private") and click Build.
- For API name, enter DynamoDBOperations.
- Leave the other settings as defaults and click Create API.
You now have an empty REST API. Next, create the resource path that clients will send requests to.
- In the left sidebar, click Resources (it should already be selected).
- Click Create resource.
- For Resource name, enter items.
- Click Create resource.
Now add the PUT method to this resource.
- With the /items resource selected, click Create method.
- For Method type, select PUT.
- For Integration type, select Lambda Function.
- Turn on Lambda proxy integration.
- For Lambda function, select your region and enter LambdaFunctionOverHttps.
- Click Create method.
What about Lambda permissions?
When you create a Lambda integration via the API Gateway console, it automatically adds a resource-based policy to your Lambda function granting API Gateway permission to invoke it. A confirmation dialog will appear asking you to allow this. Click OK to confirm.
You should now see the PUT method listed under /items in the Resources panel, with a method execution diagram showing the request flow from client to Lambda.
Don't see the PUT method?
Make sure you selected the /items resource before clicking Create method. If you accidentally created it on the root / resource, delete it and try again on /items.
help me set up the PUT method on my /items resource in API Gateway
Update the Lambda code to write items to DynamoDB
Your current Lambda function just echoes back whatever it receives. Now you need it to actually parse the incoming request body and store the item in your DynamoDB table.
- Switch back to the Lambda console and open your LambdaFunctionOverHttps function.
- In the Code tab, select all the existing code in lambda_function.py and replace it with the following:
import json
import boto3
# Connect to DynamoDB
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('lambda-apigateway')
def lambda_handler(event, context):
# Determine which HTTP method was used
http_method = event['httpMethod']
try:
if http_method == 'PUT':
# Parse the request body and store the item
body = json.loads(event['body'])
table.put_item(Item=body)
return build_response(201, {'message': 'Item created/updated successfully'})
else:
return build_response(400, {'message': f'Unsupported method: {http_method}'})
except Exception as e:
return build_response(500, {'message': str(e)})
# Helper function to format HTTP responses
def build_response(status_code, body):
return {
'statusCode': status_code,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(body)
}
What does this code do?
This function reads the HTTP method from the event that API Gateway passes in. With Lambda proxy integration enabled, the entire HTTP request (method, headers, body, path) arrives as the event dictionary.
- boto3 is the AWS SDK for Python, pre-installed in the Lambda runtime. It connects to your lambda-apigateway table.
- event['body'] contains the JSON string the client sent. json.loads() parses it into a Python dictionary.
- table.put_item() writes the parsed dictionary to DynamoDB as a new item.
- build_response() formats the return value so API Gateway can turn it into a proper HTTP response for the client.
- Click Deploy (the orange button above the code editor) to save and deploy your function.
You should see a green banner confirming that the function was updated successfully.
Deploy button greyed out?
Make sure you have actually edited the code. If you pasted but the editor did not register the change, try deleting a character and re-typing it. Then click Deploy again.
help me deploy my updated Lambda function code
✔️ Awesome, I've got everything!
Great. Double check you see the green success banner after clicking Deploy.
ⓧ I'd like to double check the full code
Here is the complete lambda_function.py at this point:
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('lambda-apigateway')
def lambda_handler(event, context):
http_method = event['httpMethod']
try:
if http_method == 'PUT':
body = json.loads(event['body'])
table.put_item(Item=body)
return build_response(201, {'message': 'Item created/updated successfully'})
else:
return build_response(400, {'message': f'Unsupported method: {http_method}'})
except Exception as e:
return build_response(500, {'message': str(e)})
def build_response(status_code, body):
return {
'statusCode': status_code,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(body)
}
Deploy the API and test with curl
Your Lambda is ready and the API method is wired up. The final piece is deploying the API to a stage, which makes it accessible at a live URL.
- Switch back to the API Gateway console and open your DynamoDBOperations API.
- Click Deploy API (the button at the top of the Resources panel).
- For Stage, select *New Stage*.
- For Stage name, enter test.
- Click Deploy.
After deploying, you will see the Invoke URL at the top of the stage editor. It follows the format https://{api-id}.execute-api.{region}.amazonaws.com/test.
- Copy your invoke URL.
What is a stage?
A stage is a named snapshot of your API deployment. You might use test for development and prod for production. Each stage gets its own invoke URL.
Before you run the next command, what do you think will happen when you send a PUT request with a product item to your new endpoint?
- Open your terminal and send a PUT request to create a product item by running this command:
curl -X PUT -H "Content-Type: application/json" -d '{"id": "001", "name": "Widget", "price": 9.99}' [[API_INVOKE_URL="https://your-api-id.execute-api.your-region.amazonaws.com/test"]]/items
You should see a 201 response with this body:
{"message": "Item created/updated successfully"}
- Confirm the item was stored by switching to the DynamoDB console.
- Click Explore table items on your lambda-apigateway table.
You should see the item with id = 001, name = Widget, and price = 9.99 listed in the table.
Seeing an Internal Server Error?
If you get a 500 error, check that your Lambda code was deployed (the green success banner appeared). Also verify that the function name in your API Gateway method integration matches LambdaFunctionOverHttps exactly.
If you see a 403 or a "Missing Authentication Token" message, double check that your curl URL ends with /items and matches the invoke URL (including the /test stage).
help me debug my curl request to API Gateway
Your API is live and writing data to DynamoDB. Next up, you will expand the function to support the full set of CRUD operations so you can read, list, and delete items too.
Expand to Full CRUD
Your API can create items, but it cannot read them back or remove them. A real microservice needs the full lifecycle: create, read, update, and delete.
In this step you will add GET and DELETE routes to your API. Along the way you will hit a classic integration gotcha: DynamoDB returns numbers as Python Decimal objects, which are not JSON-serializable. Debugging that error and fixing it teaches you how data types flow between AWS services.
In this step, get ready to:
- Create new API Gateway resources and methods for GET and DELETE operations.
- Update your Lambda function to route requests by HTTP method and resource path.
- Fix the Decimal serialization issue between DynamoDB and Python's json module.
Create GET and DELETE methods in API Gateway
Your API currently only has a PUT method on /items. You need a child resource to represent a single item, plus GET and DELETE methods so clients can retrieve and remove items.
- Return to your DynamoDBOperations API in the API Gateway console.
- In the Resources panel, select the /items resource.
- Click Create resource.
- Keep Proxy resource turned off.
- For Resource path, select /items/.
- For Resource name, enter {id}. Include the curly braces so /items/{id} is displayed.
- Click Create resource.
You should now see /{id} nested under /items in the resources tree.
Next, add a GET method on the /items resource to list all items.
- Select the /items resource (the parent, not /{id}).
- Click Create method.
- For Method type, select GET.
- For Integration type, select Lambda function.
- Turn on Lambda proxy integration.
- For Lambda function, enter LambdaFunctionOverHttps and select it.
- Click Create method.
- If prompted to give API Gateway permission to invoke your function, click OK.
Repeat the same steps for the /{id} resource. You need a GET and a DELETE method there.
- Select the /{id} resource.
- Click Create method.
- For Method type, select GET.
- For Integration type, select Lambda function.
- Turn on Lambda proxy integration.
- For Lambda function, enter LambdaFunctionOverHttps and select it.
- Click Create method.
- With /{id} still selected, click Create method again.
- For Method type, select DELETE.
- For Integration type, select Lambda function.
- Turn on Lambda proxy integration.
- For Lambda function, enter LambdaFunctionOverHttps and select it.
- Click Create method.
Now deploy these changes so they take effect on the live endpoint.
- Click Deploy API.
- For Stage, select test.
- Click Deploy.
What did you just set up?
Your API now has four method-resource combinations. PUT on /items creates items. GET on /items lists all items. GET on /items/{id} retrieves one item. DELETE on /items/{id} removes an item.
All four methods point to the same Lambda function. The function will inspect the incoming HTTP method and resource path to decide what action to take.
Add routing logic to your Lambda function
Your Lambda function currently only handles PUT. With four methods pointing to it, the function needs to inspect each request and decide whether to create, read, or delete an item.
- Return to the Lambda console and open your LambdaFunctionOverHttps function.
- In the Code tab, select all the code in lambda_function.py and delete it.
- Paste the following code as the first section of your file:
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('lambda-apigateway')
def lambda_handler(event, context):
http_method = event['httpMethod']
resource = event.get('resource', '')
try:
if http_method == 'PUT' and resource == '/items':
body = json.loads(event['body'])
table.put_item(Item=body)
return build_response(201, {'message': 'Item created/updated successfully'})
elif http_method == 'GET' and resource == '/items/{id}':
item_id = event['pathParameters']['id']
response = table.get_item(Key={'id': item_id})
if 'Item' in response:
return build_response(200, response['Item'])
else:
return build_response(404, {'message': f'Item {item_id} not found'})
What does this code do?
- event['httpMethod'] and event.get('resource', '') pull the HTTP verb and the resource template path from the incoming request. Together they identify which route was called.
- The PUT branch creates or updates an item, same as before but now guarded by a resource check.
- The GET /items/{id} branch uses table.get_item() to fetch a single item by its partition key, returning 404 if it does not exist.
- Paste this code directly below the previous block, continuing the elif chain:
elif http_method == 'GET' and resource == '/items':
response = table.scan()
return build_response(200, response.get('Items', []))
elif http_method == 'DELETE' and resource == '/items/{id}':
item_id = event['pathParameters']['id']
table.delete_item(Key={'id': item_id})
return build_response(200, {'message': f'Item {item_id} deleted'})
else:
return build_response(400, {'message': f'Unsupported route: {http_method} {resource}'})
except Exception as e:
return build_response(500, {'message': str(e)})
def build_response(status_code, body):
return {
'statusCode': status_code,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps(body)
}
What does this code do?
- The GET /items branch calls table.scan() to retrieve every item in the table.
- The DELETE branch removes a single item by its id key.
- The else branch returns 400 for any route the function does not recognize.
- build_response() wraps every return value into the format API Gateway expects: a statusCode, headers, and a JSON-encoded body.
- Click Deploy in the Lambda code editor to save and deploy your function.
Before you test, think about what you expect to happen. You stored an item earlier with a numeric price field. Do you think the GET request will return that item successfully?
- Test the GET by-id route by running this in your terminal:
curl https://[[API_INVOKE_URL="your-api-id.execute-api.your-region.amazonaws.com/test"]]/items/001
You receive a 500 Internal Server Error response. Something went wrong inside the Lambda function.
Seeing a 500 Internal Server Error? Good.
This is the designed obstacle for this step. Your code is correct, but Python's json.dumps() cannot serialize the Decimal type that DynamoDB returns for numeric fields. You will fix this in the next substep.
To confirm the exact error, check the CloudWatch Logs for your function.
- In the Lambda console, click the Monitor tab.
- Click View CloudWatch logs.
- Select the most recent log stream.
- Look for a log event containing the error. You should see a message about the Decimal type not being JSON serializable.
Why does DynamoDB return Decimal objects?
When you stored price: 9.99 via put_item(), DynamoDB saved it as a Number type. The boto3 resource interface reads Numbers back as Python decimal.Decimal objects to preserve precision.
Python's built-in json.dumps() does not know how to convert Decimal to JSON. The fix is a custom encoder that converts each Decimal to a regular Python float.
Fix the Decimal serialization error
You need three small changes: import the Decimal class, add a custom JSON encoder, and tell build_response() to use it.
- In lambda_function.py, find this line near the top:
import boto3
- Add this import directly below it:
from decimal import Decimal
- Find the line that says table = dynamodb.Table('lambda-apigateway'). Below it (and above the def lambda_handler line), paste the DecimalEncoder class:
class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return super().default(obj)
What does DecimalEncoder do?
It extends Python's built-in json.JSONEncoder. When json.dumps() encounters a Decimal value, it calls the default() method, which converts the Decimal to a regular float that JSON can represent.
Finally, update build_response() to use your new encoder.
- Scroll to the build_response function and find this line:
'body': json.dumps(body)
- Replace it with:
'body': json.dumps(body, cls=DecimalEncoder)
- Click Deploy to save and deploy the updated function.
Seeing a syntax error after Deploy?
- Make sure the DecimalEncoder class is at the module level (not indented inside another function).
- Check that from decimal import Decimal is on its own line directly below import boto3.
- Confirm cls=DecimalEncoder is inside the json.dumps() parentheses, not outside them.
Help me fix a syntax error in my Lambda function after adding DecimalEncoder.
✔️ Awesome, I've got everything!
Great. Make sure your file is deployed and move on to testing the full CRUD cycle below.
ⓧ I'd like to double check the full code
Scroll up through your code and confirm these sections are present in this order:
- Imports: json, boto3, and from decimal import Decimal.
- Table setup: dynamodb = boto3.resource('dynamodb') and table = dynamodb.Table('lambda-apigateway').
- The DecimalEncoder class with its default() method.
- The lambda_handler function routing PUT, GET (by id), GET (all), DELETE, and else.
- The build_response function with cls=DecimalEncoder in its json.dumps() call.
Now test the full CRUD cycle. Before you run the first command, predict whether the GET request will succeed this time.
- Retrieve the item you stored earlier by running:
curl https://[[API_INVOKE_URL="your-api-id.execute-api.your-region.amazonaws.com/test"]]/items/001
You should receive a 200 response with the full item JSON, including the numeric price field correctly serialized as a float.
- List all items by running:
curl https://[[API_INVOKE_URL="your-api-id.execute-api.your-region.amazonaws.com/test"]]/items
You should see a JSON array containing all items in the table.
- Delete the item by running:
curl -X DELETE https://[[API_INVOKE_URL="your-api-id.execute-api.your-region.amazonaws.com/test"]]/items/001
You should see a message confirming the item was deleted.
- Confirm the deletion by trying to GET the same item:
curl https://[[API_INVOKE_URL="your-api-id.execute-api.your-region.amazonaws.com/test"]]/items/001
You should receive a 404 response with the message that the item was not found. Full CRUD is working.
Still seeing a 500 error on GET?
- Make sure you clicked Deploy in the Lambda console after making your changes. Editing code without deploying does not update the live function.
- Check that from decimal import Decimal is at the top of the file, not inside a function.
- Confirm you redeployed the API in API Gateway to the test stage after adding the new methods.
Help me debug my Lambda CRUD function that returns a 500 error.
Your serverless microservice now handles the full item lifecycle. Next up, you will lock down this open API with key-based authentication in the Secret Mission.
Secret mission
Add API Key Authentication
Your API is live and open to the entire internet. Anyone with your invoke URL can create, read, update, and delete items. In production, you would gate access behind API keys to track consumers, enforce rate limits, and revoke access when needed. Can you lock it down?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. All resources in this project fall within the AWS Free Tier, so there are no immediate ongoing costs.
Cost warning
DynamoDB and Lambda are covered by the Always Free tier indefinitely. However, the API Gateway REST API free tier (1 million calls/month) expires after 12 months. If your AWS account is older than 12 months, leaving the API deployed could incur small charges per request.
Resources you used:
- API Gateway REST API DynamoDBOperations (deployed to test stage).
- Lambda function LambdaFunctionOverHttps.
- DynamoDB table lambda-apigateway.
- IAM role lambda-apigateway-role and policy lambda-apigateway-policy.
- API key ProductAPIKey and usage plan BasicPlan.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your API.
- All resources remain within Free Tier limits for learning and testing.
- DynamoDB and Lambda are Always Free. API Gateway is free for 1 million calls/month for your first 12 months.
- No charges will accrue unless you exceed free tier limits.
Pause - I'll come back to this later
Shut down the public endpoint while keeping your resources intact so you can resume later.
- In the API Gateway console, select your DynamoDBOperations API.
- In the main navigation pane, choose Stages.
- Select the test stage, then choose Stage actions, Delete stage.
- When prompted, enter confirm and choose Delete.
- This removes the public invoke URL. Your Lambda, DynamoDB table, and API definition remain intact. To resume, redeploy the API to a new stage.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow these steps in order.
Delete the REST API (this also removes stages, deployments, the usage plan association, and method configurations):
- In the API Gateway console, open your DynamoDBOperations API.
- In the Resources pane, choose API actions, Delete API.
- In the Delete API dialog box, enter confirm and choose Delete.
Delete the API key and usage plan:
- In the API Gateway console left navigation, choose API keys.
- Select ProductAPIKey and delete it.
- In the left navigation, choose Usage plans.
- Select BasicPlan and delete it.
Delete the Lambda function:
- Open the Functions page of the Lambda console.
- Select LambdaFunctionOverHttps.
- Choose Actions, Delete.
- Type confirm in the text input field and choose Delete.
Delete the DynamoDB table:
- Open the DynamoDB console.
- Select the lambda-apigateway table.
- Choose Delete table.
- Confirm your choice and choose Delete.
Delete the IAM role and policy:
- Open the IAM console. In the navigation pane, choose Roles.
- Select the checkbox next to lambda-apigateway-role and choose Delete at the top of the page.
- Enter the role name and choose Delete. The console automatically detaches the managed policy.
- In the navigation pane, choose Policies. Search for lambda-apigateway-policy.
- Select the policy, choose Actions, then Delete. Confirm to remove it permanently.
Nice Work!
Nice Work!
You just built and deployed a fully serverless CRUD microservice on AWS. You can hit your live API endpoint from anywhere in the world with a single curl command and create, read, update, and delete product items stored in DynamoDB.
You've learned how to:
- Wire up a REST API with Lambda proxy integration across four routes (PUT, GET, GET by id, DELETE) and deploy it to a live stage with a public invoke URL.
- Build a Python Lambda function that routes HTTP requests by method and resource path, returning proper status codes and handling errors gracefully.
- Debug the Decimal serialization mismatch between DynamoDB and Python's json module by writing a custom JSONEncoder subclass.
- Secret Mission: Secured the API with API key authentication and rate-limited it with a usage plan.
Ready to quiz yourself?