Build a Serverless CRUD API
Connect Lambda, API Gateway, and DynamoDB to build a live REST API.
Introduction
30 Second Summary
Every time you browse products online, add something to a cart, or check an order status, an API is handling that request behind the scenes. Traditionally, running those APIs meant paying for a server around the clock, even when nobody was using it.
In this project, you will build a serverless API on AWS that manages a product catalog. Your API will create, read, update, and delete products through HTTP endpoints, all without you provisioning or maintaining a single server.
What You'll Build
A fully functional serverless REST API backed by three core AWS services: Lambda, API Gateway, and DynamoDB.
By the end of this project, you'll have:
- A DynamoDB table storing product items in a NoSQL database.
- A Python Lambda function that handles create, read, update, and delete operations.
- A live, publicly accessible API URL you can call from anywhere using curl.
- Secret Mission: Add a search-by-name feature using DynamoDB FilterExpressions.
Are there any prerequisites?
None. This project walks you through everything from creating an AWS account to testing your finished API. All you need is a web browser and a terminal.
Before We Start
Before We Start...
Set Up Your AWS Account
Before you can build anything serverless, you need access to the cloud platform where your API will live. AWS provides the services that will power your entire backend without requiring a single server.
In this step, you will sign into the AWS Management Console, pick the right region for free tier coverage, and confirm that your local machine has curl ready for testing your API later.
In this step, get ready to:
- Sign in to the AWS Management Console.
- Set your region to US East (N. Virginia).
- Verify curl is available on your machine.
Create or sign in to your AWS account
AWS offers a free tier that covers everything you will build in this project. If you already have an AWS account, you can skip straight to signing in.
- Open your browser and navigate to https://aws.amazon.com.
- Click Sign In to the Console in the top-right corner.
- If you have an existing account, enter your credentials and sign in.
- If you are new to AWS, click Create a new AWS account and follow the sign-up wizard.
What do I need to sign up?
AWS requires an email address, a phone number for verification, and a credit card on file. You will not be charged for anything in this project.
The free tier covers all the services you will use today, including Lambda, API Gateway, and DynamoDB.
- After signing in, confirm you can see the AWS Management Console home page with the search bar at the top.
Can't sign in?
If you are having trouble with your credentials, try resetting your password through the sign-in page. Make sure you are signing in as a root user (not an IAM user) if this is a brand new account.
Help me troubleshoot signing in to the AWS Console.
Set your AWS region to US East (N. Virginia)
AWS runs data centers in multiple regions around the world. You need to select a specific region so all your resources are created in the same place.
- Look at the top-right corner of the console, next to your account name. You will see a region name (e.g. Ohio or Oregon).
- Click the region selector dropdown.
- Select US East (N. Virginia) from the list. Its code is us-east-1.
- Confirm the region selector now displays N. Virginia.
Why US East (N. Virginia)?
This region has the broadest free tier coverage and is the default region used in most AWS documentation and tutorials. Keeping all your resources in one region avoids cross-region issues and makes cleanup easier.
Verify curl is available on your machine
You will use curl to send HTTP requests to your live API in a later step. Both macOS and Windows 10/11 include curl by default, so you likely already have it.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Check that curl is installed by running this command:
curl --version
✔️ I see a version number
You should see output starting with something like curl 8.x.x followed by protocol and feature details. Any version works for this project.
You are all set to move on.
ⓧ Command not found
This is uncommon on modern systems, but here is how to fix it.
- On macOS, install the Xcode Command Line Tools by running:
- On Windows, curl ships with Windows 10 and later. Make sure you are using Command Prompt (not PowerShell). In PowerShell, curl is aliased to a different command. Use curl.exe --version instead if you must use PowerShell.
xcode-select --install
After installing, close your terminal, reopen it, and run curl --version again to confirm.
Help me get curl working on my machine.
Windows PowerShell gotcha
If you use PowerShell and see unexpected output (like HTML instead of version info), PowerShell is running its built-in Invoke-WebRequest alias instead of the real curl. Switch to Command Prompt, or type curl.exe (with the .exe) to bypass the alias.
Your AWS account is ready and your local machine is prepared for testing. Next up, you will create a DynamoDB table to store product data for your API.
Create a DynamoDB Table
Your AWS account is ready and your region is set. Now it's time to create the database that will store your product data.
Every API needs somewhere to store its data. You'll use DynamoDB, a fully managed NoSQL database from AWS that requires zero server maintenance and scales automatically.
In this step, get ready to:
- Create a DynamoDB table to store product items.
- Verify the table is active and ready to receive data.
Navigate to DynamoDB and create your table
- Click the search bar at the top of the AWS Management Console.
- Type DynamoDB and select DynamoDB from the results.
- Click Create table.
- For Table name, enter http-crud-tutorial-items.
- For Partition key, enter id.
- Keep the type as String (this is the default).
- Leave all other settings as default.
- Click Create table.
What is DynamoDB?
DynamoDB is a NoSQL database service from AWS. Unlike traditional SQL databases with rows and columns, DynamoDB stores data as flexible items (similar to JSON objects) inside tables.
It's fully managed, meaning AWS handles all the infrastructure, patching, and scaling for you. You just create a table and start reading and writing data.
What is a partition key?
A partition key is a unique identifier for each item in your table. Think of it like a primary key in a traditional database. Every item you store must have a unique id value so DynamoDB can find it quickly.
Verify your table is active
After clicking Create table, DynamoDB takes a few seconds to provision your table.
- Wait for the Status column to change from Creating to Active.
- Click on your table name http-crud-tutorial-items to open its details page.
- Explore the Overview tab to see the table's capacity mode, storage size, and item count.
What should I see?
On the overview page, you'll see that your table uses on-demand capacity mode by default. This means DynamoDB automatically scales to handle whatever traffic you send it.
The item count and storage size will both show 0 since you haven't added any data yet. That's exactly what you want to see at this stage.
Table stuck on Creating?
If the status hasn't changed after 30 seconds, try refreshing the page using the refresh icon in the tables list (not your browser's refresh button). DynamoDB tables typically take under 10 seconds to create.
Also confirm your region is still set to US East (N. Virginia) in the top-right corner. If it changed, switch back and look for your table there.
Still having trouble? Help me figure out why my DynamoDB table isn't showing as Active.
Your database is live and ready to store product data. Next up, you'll create the Lambda function that reads and writes to this table whenever your API receives a request.
Create a Lambda Function
Your DynamoDB table is ready to store product data. Now you need something to actually read and write to that table when HTTP requests come in.
In this step, you will create a Lambda function. This is a piece of Python code that AWS runs on demand whenever your API receives a request. You will not manage any servers.
In this step, get ready to:
- Create a Lambda function with a Python runtime.
- Configure an IAM role that grants your function permission to access DynamoDB.
- Deploy the CRUD handler code that routes requests to the correct DynamoDB operation.
Create a new Lambda function
The Lambda console lets you write and deploy functions directly in the browser. You will author a new function from scratch using the Python runtime.
- In the AWS console search bar at the top, type Lambda and select the Lambda service.
- Choose Create function.
- Select Author from scratch.
- For Function name, enter http-crud-tutorial-function.
- For Runtime, choose Python 3.14.
- For Architecture, keep x86_64 (the default).
What is AWS Lambda?
Lambda is a compute service that runs your code only when triggered. You do not provision or manage servers. AWS handles all the infrastructure, scaling your function automatically from zero to thousands of requests per second.
You only pay for the compute time your code actually uses. When no one is calling your API, Lambda costs nothing.
Configure permissions for DynamoDB access
Your Lambda function needs permission to read and write to the DynamoDB table you created. You will create an IAM role with a policy template that grants this access.
- Under Permissions, choose Change default execution role.
- Select Create a new role from AWS policy templates.
- For Role name, enter http-crud-tutorial-role.
- For Policy templates, search for and choose Simple microservice permissions.
- Choose Create function.
What is an IAM role?
An IAM role is a set of permissions that defines what an AWS service is allowed to do. By attaching the "Simple microservice permissions" policy template, you are granting your Lambda function permission to perform read and write operations on your DynamoDB table.
Without this role, your function would be blocked from accessing the table and every request would fail.
After AWS finishes creating your function, you will land on the function's detail page with a code editor at the bottom.
Don't see the code editor?
Make sure the function finished creating. The page should show a green "Successfully created" banner at the top. If you see an error about the role, double check that you selected Simple microservice permissions in the policy templates field.
Help me troubleshoot my Lambda function creation.
Deploy the CRUD handler code
The default code Lambda created is just a placeholder. You need to replace it with the Python handler that routes incoming API Gateway events to the correct DynamoDB operation (create, read, update, or delete).
- In the code editor on your function page, select all the existing code and delete it.
- Paste the following code into the editor:
import json
import boto3
from decimal import Decimal
# Set up DynamoDB client and table reference
client = boto3.client('dynamodb')
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table('http-crud-tutorial-items')
tableName = 'http-crud-tutorial-items'
def lambda_handler(event, context):
# Log the incoming event for debugging
print(event)
body = {}
statusCode = 200
headers = {
"Content-Type": "application/json"
}
try:
# Route the request based on the HTTP method and path
if event['routeKey'] == "DELETE /items/{id}":
table.delete_item(
Key={'id': event['pathParameters']['id']})
body = 'Deleted item ' + event['pathParameters']['id']
elif event['routeKey'] == "GET /items/{id}":
body = table.get_item(
Key={'id': event['pathParameters']['id']})
body = body["Item"]
responseBody = [
{'price': float(body['price']), 'id': body['id'], 'name': body['name']}]
body = responseBody
elif event['routeKey'] == "GET /items":
body = table.scan()
body = body["Items"]
responseBody = []
for items in body:
responseItems = [
{'price': float(items['price']), 'id': items['id'], 'name': items['name']}]
responseBody.append(responseItems)
body = responseBody
elif event['routeKey'] == "PUT /items":
requestJSON = json.loads(event['body'])
table.put_item(
Item={
'id': requestJSON['id'],
'price': Decimal(str(requestJSON['price'])),
'name': requestJSON['name']
})
body = 'Put item ' + requestJSON['id']
except KeyError:
statusCode = 400
body = 'Unsupported route: ' + event['routeKey']
body = json.dumps(body)
res = {
"statusCode": statusCode,
"headers": {
"Content-Type": "application/json"
},
"body": body
}
return res
What does this code do?
This function is the brain of your API. Every time someone calls your API, Lambda runs this function and passes in an event object containing the request details.
- event['routeKey'] tells the function which HTTP method and path were called (like GET /items or DELETE /items/{id}).
- The if/elif chain routes each request to the matching DynamoDB operation: delete_item, get_item, scan (list all), or put_item (create/update).
- The function returns a JSON response with a status code and body, which API Gateway sends back to the caller.
- If the route does not match any known path, it catches the KeyError and returns a 400 status with an error message.
- Choose Deploy to save and deploy your function.
You should see a green banner that says your function was updated successfully.
Seeing an error when you deploy?
Check for indentation issues. Python is whitespace-sensitive, so every line must be indented exactly as shown. Make sure you deleted all of the default code before pasting.
Help me fix my Lambda function deployment error.
✔️ Awesome, I've got everything!
Great. Make sure you saw the green success banner after clicking Deploy.
ⓧ I'd like to double check the full code
Here is the complete lambda_function.py code. Compare it line by line with what you have in the editor.
import json
import boto3
from decimal import Decimal
client = boto3.client('dynamodb')
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table('http-crud-tutorial-items')
tableName = 'http-crud-tutorial-items'
def lambda_handler(event, context):
print(event)
body = {}
statusCode = 200
headers = {
"Content-Type": "application/json"
}
try:
if event['routeKey'] == "DELETE /items/{id}":
table.delete_item(
Key={'id': event['pathParameters']['id']})
body = 'Deleted item ' + event['pathParameters']['id']
elif event['routeKey'] == "GET /items/{id}":
body = table.get_item(
Key={'id': event['pathParameters']['id']})
body = body["Item"]
responseBody = [
{'price': float(body['price']), 'id': body['id'], 'name': body['name']}]
body = responseBody
elif event['routeKey'] == "GET /items":
body = table.scan()
body = body["Items"]
responseBody = []
for items in body:
responseItems = [
{'price': float(items['price']), 'id': items['id'], 'name': items['name']}]
responseBody.append(responseItems)
body = responseBody
elif event['routeKey'] == "PUT /items":
requestJSON = json.loads(event['body'])
table.put_item(
Item={
'id': requestJSON['id'],
'price': Decimal(str(requestJSON['price'])),
'name': requestJSON['name']
})
body = 'Put item ' + requestJSON['id']
except KeyError:
statusCode = 400
body = 'Unsupported route: ' + event['routeKey']
body = json.dumps(body)
res = {
"statusCode": statusCode,
"headers": {
"Content-Type": "application/json"
},
"body": body
}
return res
Your Lambda function is deployed and ready to handle CRUD requests. Next up, you will create an HTTP API that gives your function a public URL people can call.
Create an HTTP API in API Gateway
Your Lambda function is ready to handle requests, but there is no way for the outside world to reach it yet. API Gateway acts as the front door, accepting HTTP requests from the internet and forwarding them to your Lambda function.
In this step, you will create an HTTP API, define four routes that map to the CRUD operations in your Lambda code, and wire everything together with a Lambda integration.
In this step, get ready to:
- Create an HTTP API in the API Gateway console.
- Define four routes that map HTTP methods and paths to your Lambda function.
- Create and attach a Lambda integration to all four routes.
Create the HTTP API
An HTTP API is a lightweight, low-cost entry point that routes incoming web requests to your backend. You will create an empty API first, then add routes and integrations in the following substeps.
- In the AWS search bar at the top of the console, type API Gateway and select the service.
- Choose Create API.
- Under HTTP API, choose Build.
- For API name, enter http-crud-tutorial-api.
- For IP address type, select IPv4.
- Choose Next.
- On the Configure routes page, choose Next to skip route creation for now.
- Review the default stage ($default) that API Gateway creates for you, then choose Next.
- Choose Create.
What is an HTTP API?
An HTTP API is one of two API types offered by API Gateway. It is simpler, faster, and cheaper than a REST API, making it ideal for straightforward Lambda proxy integrations like this project.
The $default stage auto-deploys every change you make, so you never need to manually deploy your API.
You should now see your new API's details page with an Invoke URL displayed at the top.
Don't see your API?
Make sure you selected HTTP API and not REST API on the Create API page. REST APIs have a different console layout and workflow.
Also confirm your region is still set to US East (N. Virginia) in the top-right corner.
help me find my HTTP API in the API Gateway console
Create four routes
Routes tell API Gateway which Lambda function to call based on the HTTP method and path in the request. You need four routes to match the four operations in your Lambda code.
- In the left sidebar of your API, choose Routes.
- Choose Create.
- For Method, choose GET.
- For the path, enter /items/{id}.
- Choose Create.
What is {id} in the path?
The {id} portion is a path parameter. API Gateway extracts the value from the URL and passes it to your Lambda function inside event['pathParameters']['id'].
Now repeat the process for the remaining three routes.
- Choose Create again.
- For Method, choose GET. For the path, enter /items. Choose Create.
- For Method, choose PUT. For the path, enter /items. Choose Create.
- For Method, choose DELETE. For the path, enter /items/{id}. Choose Create.
You should now see all four routes listed on the Routes page:
- GET /items/{id}
- GET /items
- PUT /items
- DELETE /items/{id}
Missing a route?
Double-check that you chose the correct HTTP method from the dropdown for each route. A common mistake is creating two GET routes with the same path or forgetting the /{id} suffix.
You can delete a route by selecting it and choosing Delete, then recreate it.
help me fix my API Gateway routes
Create and attach a Lambda integration
An integration connects your routes to a backend resource. You will create a single Lambda integration and then attach it to all four routes so every request reaches your function.
- In the left sidebar, choose Integrations.
- Choose Manage integrations, then choose Create.
- Skip Attach this integration to a route for now.
- For Integration type, choose Lambda function.
- For Lambda function, enter http-crud-tutorial-function.
- Choose Create.
Why one integration for all routes?
Your Lambda function uses event['routeKey'] to determine which operation to run. This means a single function can handle all four routes.
In production, you would typically create separate functions for each route to improve isolation and make debugging easier.
Now attach the integration to each of your four routes.
- In the left sidebar, choose Integrations.
- Choose a route (for example, GET /items/{id}).
- Under Choose an existing integration, select http-crud-tutorial-function.
- Choose Attach integration.
- Repeat for the remaining three routes: GET /items, PUT /items, and DELETE /items/{id}.
Once finished, all four routes should show an AWS Lambda integration attached.
Integration not appearing in the dropdown?
Make sure you created the integration in the Manage integrations section first. If you skipped that step, the dropdown under Choose an existing integration will be empty.
Also verify you are looking at your http-crud-tutorial-api API and not a different one.
help me attach my Lambda integration to my API Gateway routes
Your API is now fully wired up. Every HTTP request that hits one of your four routes will be forwarded to your Lambda function, which reads the route key and performs the matching DynamoDB operation. Next up, you will test your live API with real HTTP requests.
Test Your Live API
Your serverless chain is complete. API Gateway routes are connected to your Lambda function, which is connected to your DynamoDB table.
Now it's time to prove everything works by calling your live API from your terminal using curl. You'll run through the full CRUD cycle: create a product, read it back, list all products, and delete it.
In this step, get ready to:
- Find your API's invoke URL in the API Gateway console.
- Create, retrieve, and list items using curl.
- Delete an item and verify it's gone.
Find your API's invoke URL
Every HTTP API in API Gateway has a unique invoke URL. This is the public address that anyone on the internet can use to reach your API.
- Return to the API Gateway console in your browser.
- Click on http-crud-tutorial-api to open your API.
- On the API details page, find the Invoke URL field. It looks like https://abcdef123.execute-api.us-east-1.amazonaws.com.
- Click the copy icon next to the URL to copy it to your clipboard.
What is the invoke URL?
This URL is the public entry point to your API. When someone sends an HTTP request to this URL, API Gateway receives it, routes it to the correct Lambda function based on the path and method, and returns the response.
Your invoke URL is unique to your API. It will be different from the example shown above.
Don't see an invoke URL?
Make sure you're on the API's main details page, not inside a specific route. Click your API name in the breadcrumb navigation at the top to go back to the overview page.
help me find my API's invoke URL in the API Gateway console
Create and retrieve items with curl
With your invoke URL copied, open your terminal. You'll send HTTP requests to your live API and watch your Lambda function create and fetch data from DynamoDB in real time.
- Create a product item in your DynamoDB table by running this command:
curl -X "PUT" -H "Content-Type: application/json" -d '{"id": "123", "price": 12345, "name": "myitem"}' [[API_URL="https://abcdef123.execute-api.us-east-1.amazonaws.com"]]/items
What does this command do?
- curl -X "PUT" sends an HTTP PUT request to your API.
- -H "Content-Type: application/json" tells the API that the request body is JSON.
- -d '{...}' is the JSON body containing the item's id, price, and name.
- The URL ends with /items, which matches your PUT /items route in API Gateway.
You should see "Put item 123" in your terminal output. This confirms that Lambda stored the item in DynamoDB.
- Retrieve the item you just created by running this command:
curl [[API_URL="https://abcdef123.execute-api.us-east-1.amazonaws.com"]]/items/123
You should see a JSON response containing your item's data, including the id, price, and name fields.
- List all items in your table by running this command:
curl [[API_URL="https://abcdef123.execute-api.us-east-1.amazonaws.com"]]/items
You should see a JSON array containing the item you created. If you added multiple items, they would all appear here.
Seeing "Internal Server Error"?
- Double-check that your invoke URL is correct and ends with /items or /items/123. A typo in the URL is the most common cause.
- Verify your Lambda function was deployed successfully. Return to the Lambda console and check for any error banners at the top of the page.
- Make sure the DynamoDB table name in your Lambda code matches http-crud-tutorial-items exactly.
help me debug my serverless API that returns Internal Server Error
Delete an item and confirm deletion
The final CRUD operation is delete. You'll remove the item from DynamoDB, then list all items again to confirm the table is empty.
- Delete the item you created by running this command:
curl -X "DELETE" [[API_URL="https://abcdef123.execute-api.us-east-1.amazonaws.com"]]/items/123
You should see "Deleted item 123" in the response.
- Confirm the item is gone by listing all items again:
curl [[API_URL="https://abcdef123.execute-api.us-east-1.amazonaws.com"]]/items
You should see an empty list [] in the response. Your item has been permanently removed from DynamoDB.
What just happened?
You just completed a full CRUD cycle against a live serverless backend. Each curl command triggered a different path through your Lambda function, which performed the matching DynamoDB operation.
No servers were running while you waited between commands. Lambda spun up only when a request arrived, processed it, and shut down again. That's serverless in action.
Item still showing after delete?
- Make sure you used the same id (123) in both the PUT and DELETE commands.
- Check that the DELETE curl command includes -X "DELETE". Without this flag, curl sends a GET request by default.
help me debug why my DynamoDB item is not being deleted
Secret mission
Add Search by Name
Your API can create, read, update, and delete products. But what if you want to find a product by name without knowing its ID? In this secret mission, you will add a search endpoint that filters products using DynamoDB's FilterExpression.
Clean Up Your Resources
Clean Up Your Resources
All resources in this project fall within the AWS Free Tier. Your Lambda function only runs when called, DynamoDB charges nothing for idle storage under 25 GB, and API Gateway is free under 1 million calls per month for the first 12 months.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- DynamoDB table: http-crud-tutorial-items
- Lambda function: http-crud-tutorial-function
- HTTP API: http-crud-tutorial-api
- IAM role: http-crud-tutorial-role
- CloudWatch log group: /aws/lambda/http-crud-tutorial-function
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your API available.
- Your Lambda function only executes when someone calls your API. No calls means no charges.
- DynamoDB stores your items for free under 25 GB (always free, not limited to 12 months).
- API Gateway HTTP APIs are free under 1 million calls per month for the first 12 months of your AWS account.
Pause - I'll come back to this later
No action needed. Serverless resources do not run when idle, so there is nothing to pause.
- Your Lambda function consumes zero compute when no one calls the API.
- DynamoDB does not charge for idle tables under the free tier limits.
- You can come back anytime and your API will still respond to requests.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below in order.
Delete the DynamoDB table
- Open the DynamoDB console (search "DynamoDB" in the AWS search bar).
- In the left navigation pane, choose Tables
- Select the checkbox next to http-crud-tutorial-items.
- Choose Delete.
- Confirm the deletion by entering delete and choosing Delete table.
Delete the Lambda function
- Open the Lambda console (search "Lambda" in the AWS search bar).
- On the Functions page, select http-crud-tutorial-function.
- Choose Actions, then Delete.
- In the confirmation dialog, enter delete and choose Delete.
Delete the HTTP API
- Open the API Gateway console (search "API Gateway" in the AWS search bar).
- On the APIs page, select http-crud-tutorial-api.
- Choose Actions, then choose Delete.
- Choose Delete to confirm.
Delete the CloudWatch log group
- Open the CloudWatch console (search "CloudWatch" in the AWS search bar).
- In the navigation pane, choose Logs, then Log groups.
- Select the log group named /aws/lambda/http-crud-tutorial-function.
- Choose Actions, then choose Delete.
- Confirm the deletion when prompted.
Delete the IAM role
- Open the IAM console (search "IAM" in the AWS search bar).
- In the navigation pane, choose Roles.
- Select the checkbox next to http-crud-tutorial-role.
- Choose Delete at the top of the page.
- Enter the role name http-crud-tutorial-role in the confirmation field and choose Delete.
That's a Wrap!
That's a Wrap!
Nice work! You just deployed a fully functional serverless backend to the cloud without touching a single server.
You've learned how to:
- Built a DynamoDB NoSQL table and a Python Lambda function that handles full CRUD operations (create, read, update, delete).
- Created an HTTP API with API Gateway to expose your Lambda to the internet as a live, publicly accessible endpoint.
- Connected three core AWS serverless services through event-driven architecture and IAM roles, proving your API works end-to-end with curl.
- Added a search-by-name feature using DynamoDB FilterExpression to filter products without provisioning a new index.
Ready to quiz yourself?