Build a Serverless Microservices Backend
Deploy two independent serverless microservices on AWS with APIs and databases.
Introduction
30 Second Summary
Think about how you order something online. One system handles the product catalog. A completely separate system handles your order. If the catalog goes down for maintenance, your existing orders are still safe. That separation is the core idea behind microservices.
In this project, you will build two independent serverless microservices on AWS. You will create a Products service and an Orders service, each with its own database, its own business logic, and its own live API endpoint that you can call from your machine.
What You'll Build
A serverless backend with two independent microservices (Products and Orders), each powered by its own DynamoDB table, Lambda function, and API Gateway HTTP API.
By the end of this project, you'll have:
- Two DynamoDB tables acting as independent data stores, one for each service.
- Two Lambda functions handling create, read, and delete operations through Python code.
- Two live HTTP APIs that you can call from PowerShell on your own machine to interact with each service independently.
- Secret Mission: Add inter-service communication where the Orders service calls the Products API to verify a product exists before accepting an order.
Are there any prerequisites?
You need an AWS account created within the last 3 weeks. This project uses only Always Free tier services, so there is no cost involved.
Set Up Your DynamoDB Tables
Every microservice needs its own dedicated data store. This separation is what allows each service to be developed, deployed, and scaled independently.
In this step, you'll create two DynamoDB tables in AWS. One stores product data, and the other stores order data.
In this step, get ready to:
- Create a Products DynamoDB table.
- Create an Orders DynamoDB table.
- Verify both tables are active and ready to use.
Create the Products table
DynamoDB is a fully managed NoSQL database service from AWS. You do not need to install anything or manage servers. Each table you create acts as an independent data store for one microservice.
- Open your browser and navigate to the DynamoDB console at https://console.aws.amazon.com/dynamodb/.
- Sign in with your AWS account credentials if prompted.
- Choose Create table.
- For Table name, enter products.
- For Partition key, enter id and leave the type as String.
- For Table settings, keep the default selection of Default settings.
- Choose Create table.
- Wait for the table status to show Active. This takes about 5-10 seconds.
What is a partition key?
A partition key is a unique identifier that DynamoDB uses to distribute and locate items in a table. Every item in the table must have a unique value for this key.
In your products table, the id partition key means each product will be identified by a unique ID string like p001.
Create the Orders table
- In the left navigation pane, choose Tables.
- Choose Create table.
- For Table name, enter orders.
- For Partition key, enter id and leave the type as String.
- For Table settings, keep the default selection of Default settings.
- Choose Create table.
- Wait for the table status to show Active.
Verify both tables exist
- In the left navigation pane, choose Tables.
- Confirm that both products and orders appear in the tables list with Active status.
Why does each service get its own table?
This is a core microservices principle: services do not share databases. If the Products service and Orders service shared a single table, a schema change in one could break the other.
Separate tables let each service evolve independently. You can change the Orders table structure without touching the Products service at all.
Table stuck on Creating?
DynamoDB tables usually become Active within seconds. If a table stays in Creating status for more than a minute, refresh the page.
If the table creation fails, double-check that your table name is exactly products or orders (all lowercase, no spaces) and that the partition key is id with type String.
Help me troubleshoot my DynamoDB table creation.
Your data stores are ready. Next up, you'll build the Products Lambda function that reads from and writes to the products table.
Build the Products Microservice Lambda Function
Your databases are ready and waiting for data. Now you need something to actually read and write to those tables.
In this step, you will create a Lambda function that acts as the brain of your Products microservice. It handles incoming API requests and performs the correct database operation.
In this step, get ready to:
- Create the Products Lambda function with the correct permissions.
- Deploy Python code that handles CRUD operations for products.
- Test the function with sample events to confirm it works.
Create the Products Lambda function
AWS Lambda lets you run code without managing servers. You give it a function, and AWS runs it for you whenever it gets triggered.
- Open the Lambda console at https://console.aws.amazon.com/lambda.
- Choose Create function.
- Keep Author from scratch selected.
- For Function name, enter products-service.
- For Runtime, choose Python 3.13.
- Under Permissions, expand Change default execution role.
- Select Create a new role from AWS policy templates.
- For Role name, enter products-service-role.
- For Policy templates, search for and choose Simple microservice permissions.
- Choose Create function.
Wait a few seconds for Lambda to finish creating your function. You will see a green banner confirming the function was created successfully.
What is Simple microservice permissions?
This is a built-in IAM policy template that grants your Lambda function permission to interact with DynamoDB. Without it, your function would be blocked from reading or writing to your products table.
Don't see Python 3.13 in the list?
Make sure you are in a region that supports Python 3.13 (most regions do). If you only see older versions, try switching your region to us-east-1 using the dropdown in the top-right corner of the console.
Help me find the Python 3.13 runtime
Add the Products function code
Lambda created a placeholder function for you. You will now replace it with code that handles four operations on products: create, read one, read all, and delete.
- In the Code source editor, select the lambda_function.py tab.
- Select all the existing code and replace it with the following:
import json
import boto3
from decimal import Decimal
# Connect to DynamoDB and target the products table
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("products")
def lambda_handler(event, context):
body = {}
status_code = 200
headers = {"Content-Type": "application/json"}
try:
# Determine which route was called
route_key = event.get("routeKey", "")
if route_key == "DELETE /products/{id}":
# Delete a single product by ID
product_id = event["pathParameters"]["id"]
table.delete_item(Key={"id": product_id})
body = f"Deleted product {product_id}"
elif route_key == "GET /products/{id}":
# Get a single product by ID
product_id = event["pathParameters"]["id"]
response = table.get_item(Key={"id": product_id})
item = response.get("Item")
if item:
body = {
"id": item["id"],
"name": item["name"],
"price": float(item["price"]),
}
else:
status_code = 404
body = f"Product {product_id} not found"
elif route_key == "GET /products":
# Get all products
response = table.scan()
items = response.get("Items", [])
body = [
{
"id": item["id"],
"name": item["name"],
"price": float(item["price"]),
}
for item in items
]
elif route_key == "PUT /products":
# Create a new product
request_body = json.loads(event["body"])
table.put_item(
Item={
"id": request_body["id"],
"name": request_body["name"],
"price": Decimal(str(request_body["price"])),
}
)
body = f"Created product {request_body['id']}"
else:
status_code = 400
body = f"Unsupported route: {route_key}"
except KeyError as e:
status_code = 400
body = f"Missing required field: {str(e)}"
except Exception as e:
status_code = 500
body = f"Error: {str(e)}"
return {
"statusCode": status_code,
"headers": headers,
"body": json.dumps(body),
}
What does this code do?
This function is the entire backend for your Products microservice. It reads the routeKey from incoming API requests to decide what to do.
- PUT /products creates a new product in DynamoDB. It uses Decimal for the price because DynamoDB does not accept regular Python floats.
- GET /products scans the entire table and returns all products.
- GET /products/{id} fetches one specific product by its ID.
- DELETE /products/{id} removes a product from the table.
The boto3 library is the AWS SDK for Python. It comes pre-installed in every Lambda Python runtime, so you do not need to install anything extra.
- Choose Deploy to save and deploy your function code.
You should see a green banner that says the function was updated successfully.
Seeing a syntax error on deploy?
Make sure you replaced ALL the existing code, including the default lambda_handler that Lambda generated. If any of the original code remains mixed in, you will get errors.
Double-check that the indentation is correct. Python is sensitive to spaces.
Help me debug my deploy error
Test the Products function
Before connecting this function to a public URL, you can test it right here in the Lambda console using test events. A test event is a JSON payload that simulates an incoming API request.
- Choose the Test tab.
- Under Test event, select Create new event.
- For Event name, enter TestCreateProduct.
- Replace the default JSON with the following:
{
"routeKey": "PUT /products",
"body": "{\"id\": \"p001\", \"name\": \"Wireless Mouse\", \"price\": 29.99}"
}
What does this test event do?
This simulates an API Gateway request coming in on the PUT /products route. The body field contains a JSON string with the product details. Your function will parse this and write a new product to DynamoDB.
- Choose Save, then choose Test.
- Under Execution result, expand Details.
You should see a response with statusCode 200 and a body containing "Created product p001".
Now test that you can read the product back. Create a second test event:
- Under Test event, choose Create new event.
- For Event name, enter TestGetAllProducts.
- Replace the default JSON with the following:
{
"routeKey": "GET /products"
}
- Choose Save, then choose Test.
The response should show statusCode 200 with a body containing the Wireless Mouse product you just created.
Seeing statusCode 500 or an error?
A 500 error usually means a permissions problem. Confirm you chose Simple microservice permissions when creating the function. If you missed it, you can delete this function and create it again with the correct policy template.
If you see a 400 error mentioning a missing field, double-check your test event JSON matches the examples above exactly.
Help me fix my Lambda test error
Your Products microservice logic is complete and tested. Next, you will expose it to the internet through API Gateway so anyone can call it with an HTTP request.
Expose the Products Service via API Gateway
Your Products Lambda function is deployed and tested. Right now though, it only lives inside your AWS account. Nobody on the internet can reach it.
API Gateway is the service that creates a public URL for your Lambda function. It receives HTTP requests from the outside world, routes them to your function, and returns the response.
In this step, get ready to:
- Create an HTTP API in API Gateway.
- Define routes and connect them to your Lambda function.
- Test your live Products API from PowerShell.
Create the Products HTTP API
An HTTP API is the lightweight, low-cost flavor of API Gateway. It gives your Lambda function a public endpoint with automatic deployments.
- Open the API Gateway console at https://console.aws.amazon.com/apigateway.
- Choose Create API.
- Find the HTTP API card and choose Build.
- For API name, enter products-api.
- Choose Next.
- On the Configure routes screen, choose Next to skip route creation. You will create routes manually in the next substep.
- Review the stage that API Gateway creates for you, then choose Next.
- Choose Create.
What just happened?
You created an empty HTTP API named products-api. API Gateway automatically created a $default stage with automatic deployments enabled. This means any routes you add will go live immediately without a separate deploy step.
Create routes and attach the integration
Routes tell API Gateway which requests to forward to your Lambda function. Each route is an HTTP method paired with a resource path.
- Choose Routes in the left navigation panel.
- Choose Create.
- For Method, choose GET.
- For the path, enter /products.
- Choose Create.
- Repeat the same steps to create three more routes:
You should now see four routes listed. Next, you will create a Lambda integration and attach it to all four routes.
- Choose Integrations in the left navigation panel.
- Choose Manage integrations, then choose Create.
- Skip Attach this integration to a route for now. You will attach it in the next step.
- For Integration type, choose Lambda function.
- For Lambda function, enter products-service.
- Choose Create.
Now attach this integration to each of your four routes.
- Return to Integrations in the left navigation.
- Choose a route (for example, GET /products).
- Under Choose an existing integration, select products-service.
- Choose Attach integration.
- Repeat these steps for the remaining three routes (GET /products/{id}, PUT /products, DELETE /products/{id}).
What does an integration do?
An integration connects a route to a backend resource. Here, one Lambda integration handles all four routes. When API Gateway receives a request on any route, it forwards the event to your products-service function.
Your Lambda code then uses the routeKey field in the event to decide which operation to run.
Don't see products-service in the dropdown?
Make sure you are in the same AWS region where you created the Lambda function. Check the region selector in the top-right corner of the console.
Help me find my products-service Lambda function in the integration dropdown.
Get your invoke URL and test from PowerShell
Your API is live. Every HTTP API gets an invoke URL that you can call from anywhere on the internet.
- Choose API: products-api in the breadcrumb navigation at the top to return to the API details page.
- Find the Invoke URL shown on the details page. It looks like https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com.
- Copy this URL. You will use it in the commands below.
What is the invoke URL?
The invoke URL is the public address of your API. Anyone with this URL can send HTTP requests to your Products microservice. API Gateway handles all the networking, scaling, and routing to your Lambda function automatically.
- Open PowerShell on your Windows machine by pressing the Windows key, typing PowerShell, and pressing Enter.
- Create a product by running this command:
Invoke-RestMethod -Method PUT -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products" -ContentType "application/json" -Body '{"id": "p001", "name": "Wireless Mouse", "price": 29.99}'
What does this command do?
Invoke-RestMethod is PowerShell's built-in tool for making HTTP requests. It sends a PUT request to your Products API with a JSON body containing a product's ID, name, and price.
- Retrieve all products by running this command:
Invoke-RestMethod -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products"
You should see your Wireless Mouse product returned in the response. The output confirms that data flows from PowerShell through API Gateway, into your Lambda function, and down to DynamoDB.
Seeing an error in PowerShell?
Double-check that your invoke URL does not have a trailing slash before /products. The correct format is https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/products with no double slashes.
If you see a "Not Found" message, confirm all four routes are attached to the products-service integration in the API Gateway console under Integrations.
Help me debug my PowerShell API Gateway error.
Your Products microservice is now live on the internet with its own URL. Next up, you will build the Orders microservice and give it the same treatment.
Build and Deploy the Orders Microservice
Your Products microservice is live and responding to requests from the internet. Now you will build the second half of your architecture: the Orders service.
The Orders microservice follows the exact same pattern as Products. It gets its own Lambda function, its own API Gateway HTTP API, and it talks exclusively to the orders DynamoDB table. This independence is the whole point of microservices.
In this step, get ready to:
- Create and deploy the Orders Lambda function.
- Create the Orders HTTP API with routes and integration.
- Test the full Orders service end-to-end from PowerShell.
Create the Orders Lambda function
You will create a second Lambda function that handles CRUD operations for orders. The process is identical to the Products function you built earlier.
- Open the Lambda console at https://console.aws.amazon.com/lambda.
- Choose Create function.
- Keep Author from scratch selected.
- For Function name, enter orders-service.
- For Runtime, choose Python 3.13.
- Under Permissions, choose Change default execution role.
- Select Create a new role from AWS policy templates.
- For Role name, enter orders-service-role.
- For Policy templates, choose Simple microservice permissions.
- Choose Create function.
Why Simple microservice permissions again?
This policy template automatically grants the Lambda function permission to interact with DynamoDB. Each service gets its own IAM role so you can tighten permissions independently later.
Now you will replace the default code with the Orders service logic.
- In the Code source pane, select the lambda_function.py file.
- Select all of the existing code and replace it with the following:
import json
import boto3
from decimal import Decimal
# Connect to DynamoDB and reference the orders table
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("orders")
def lambda_handler(event, context):
body = {}
status_code = 200
headers = {"Content-Type": "application/json"}
try:
# Determine which API route was called
route_key = event.get("routeKey", "")
if route_key == "DELETE /orders/{id}":
# Delete an order by its ID
order_id = event["pathParameters"]["id"]
table.delete_item(Key={"id": order_id})
body = f"Deleted order {order_id}"
elif route_key == "GET /orders/{id}":
# Retrieve a single order by ID
order_id = event["pathParameters"]["id"]
response = table.get_item(Key={"id": order_id})
item = response.get("Item")
if item:
body = {
"id": item["id"],
"productId": item["productId"],
"quantity": int(item["quantity"]),
"customerName": item["customerName"],
}
else:
status_code = 404
body = f"Order {order_id} not found"
elif route_key == "GET /orders":
# Retrieve all orders from the table
response = table.scan()
items = response.get("Items", [])
body = [
{
"id": item["id"],
"productId": item["productId"],
"quantity": int(item["quantity"]),
"customerName": item["customerName"],
}
for item in items
]
elif route_key == "PUT /orders":
# Create a new order
request_body = json.loads(event["body"])
table.put_item(
Item={
"id": request_body["id"],
"productId": request_body["productId"],
"quantity": Decimal(str(request_body["quantity"])),
"customerName": request_body["customerName"],
}
)
body = f"Created order {request_body['id']}"
else:
status_code = 400
body = f"Unsupported route: {route_key}"
except KeyError as e:
status_code = 400
body = f"Missing required field: {str(e)}"
except Exception as e:
status_code = 500
body = f"Error: {str(e)}"
# Return the HTTP response
return {
"statusCode": status_code,
"headers": headers,
"body": json.dumps(body),
}
What does this code do?
This function is structured identically to your Products service. It reads the routeKey from the incoming HTTP API event to determine which operation to run.
- PUT /orders creates a new order with fields for productId, quantity, and customerName.
- GET /orders scans the table and returns all orders.
- GET /orders/{id} retrieves a single order by its partition key.
- DELETE /orders/{id} removes an order from the table.
Quantity is stored using Decimal because DynamoDB does not accept Python floats for number types.
- Choose Deploy in the DEPLOY section above the code editor to save the function.
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 of your function. The Deploy button appears in the DEPLOY section directly above the code editor.
If you still have trouble, help me find the Deploy button in Lambda.
Create the Orders HTTP API
With the function deployed, you need an HTTP API to expose it to the internet. You will create routes and wire them to the Lambda function.
- Open the API Gateway console at https://console.aws.amazon.com/apigateway.
- Choose Create API.
- For HTTP API, choose Build.
- For API name, enter orders-api.
- Choose Next.
- For Configure routes, choose Next to skip (you will create routes manually).
- Review the stage and choose Next.
- Choose Create.
Now create the four routes that your Orders API needs.
- Choose Routes in the left navigation.
- Choose Create.
- For Method, choose GET. For the path, enter /orders. Choose Create.
- Repeat to create GET /orders/{id}.
- Repeat to create PUT /orders.
- Repeat to create DELETE /orders/{id}.
Next, create a Lambda integration and attach it to all four routes.
- Choose Integrations in the left navigation.
- Choose Manage integrations, then choose Create.
- For Integration type, choose Lambda function.
- For Lambda function, enter orders-service.
- Choose Create.
Now attach the integration to each route.
- Return to Integrations and choose a route.
- Under Choose an existing integration, choose orders-service.
- Choose Attach integration.
- Repeat for all four routes until each one shows the Lambda integration attached.
Integration not showing for a route?
Make sure you select each route individually and attach the integration one at a time. The integration must be attached to every route separately.
If you are stuck, help me attach my Lambda integration to API Gateway routes.
Test the Orders service end-to-end
Your Orders API is live. Time to test it from your local machine using PowerShell.
- Choose API: orders-api in the breadcrumb to return to the API details page.
- Copy the Invoke URL shown on the details page. It looks like https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com.
Open PowerShell on your Windows machine. Create an order by running this command:
Invoke-RestMethod -Method PUT -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders" -ContentType "application/json" -Body '{"id": "o001", "productId": "p001", "quantity": 2, "customerName": "Alice"}'
You should see the response Created order o001.
- Retrieve all orders by running:
Invoke-RestMethod -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders"
You should see the order you just created returned as a JSON object with id, productId, quantity, and customerName fields.
- Delete the order by running:
Invoke-RestMethod -Method DELETE -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders/o001"
You should see Deleted order o001 confirming the deletion.
Getting a 500 Internal Server Error?
This usually means the Lambda function cannot access the DynamoDB table. Check that you selected Simple microservice permissions when creating the role. Also verify the table name in your code is exactly orders (lowercase, matching the DynamoDB table you created in Step 1).
If you are still stuck, help me debug my Orders service 500 error.
✔️ Awesome, I've got everything!
Great. Double check you have both the Products and Orders services deployed and responding to requests from their own Invoke URLs.
ⓧ I'd like to double check the full code
Here is the complete Orders service code that should be in your lambda_function.py:
import json
import boto3
from decimal import Decimal
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("orders")
def lambda_handler(event, context):
body = {}
status_code = 200
headers = {"Content-Type": "application/json"}
try:
route_key = event.get("routeKey", "")
if route_key == "DELETE /orders/{id}":
order_id = event["pathParameters"]["id"]
table.delete_item(Key={"id": order_id})
body = f"Deleted order {order_id}"
elif route_key == "GET /orders/{id}":
order_id = event["pathParameters"]["id"]
response = table.get_item(Key={"id": order_id})
item = response.get("Item")
if item:
body = {
"id": item["id"],
"productId": item["productId"],
"quantity": int(item["quantity"]),
"customerName": item["customerName"],
}
else:
status_code = 404
body = f"Order {order_id} not found"
elif route_key == "GET /orders":
response = table.scan()
items = response.get("Items", [])
body = [
{
"id": item["id"],
"productId": item["productId"],
"quantity": int(item["quantity"]),
"customerName": item["customerName"],
}
for item in items
]
elif route_key == "PUT /orders":
request_body = json.loads(event["body"])
table.put_item(
Item={
"id": request_body["id"],
"productId": request_body["productId"],
"quantity": Decimal(str(request_body["quantity"])),
"customerName": request_body["customerName"],
}
)
body = f"Created order {request_body['id']}"
else:
status_code = 400
body = f"Unsupported route: {route_key}"
except KeyError as e:
status_code = 400
body = f"Missing required field: {str(e)}"
except Exception as e:
status_code = 500
body = f"Error: {str(e)}"
return {
"statusCode": status_code,
"headers": headers,
"body": json.dumps(body),
}
You now have two completely independent microservices running on AWS. Next up, you will put them both through their paces to prove they operate autonomously.
Test the Full Microservices System
Both microservices are deployed and running with their own APIs, Lambda functions, and databases. Now it's time to put the full system through its paces with realistic data.
You'll populate the Products service with a small catalog, then create orders that reference those products. The real test comes when you delete a product and confirm that the Orders service is completely unaffected.
In this step, get ready to:
- Create sample products in the Products service.
- Create orders that reference product IDs.
- Demonstrate service independence by deleting a product without affecting orders.
Create sample products
A single product isn't much of a catalog. You'll add three products to simulate a realistic inventory that the Orders service can reference.
- Return to PowerShell on your Windows machine.
- Create three products by running these commands (replace the URL with your products-api Invoke URL from the earlier step):
Invoke-RestMethod -Method PUT -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products" -ContentType "application/json" -Body '{"id": "p001", "name": "Wireless Mouse", "price": 29.99}'
Invoke-RestMethod -Method PUT -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products" -ContentType "application/json" -Body '{"id": "p002", "name": "Mechanical Keyboard", "price": 79.99}'
Invoke-RestMethod -Method PUT -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products" -ContentType "application/json" -Body '{"id": "p003", "name": "USB-C Hub", "price": 49.99}'
What do these commands do?
Each command sends a PUT request to your Products API, creating a new item in the products DynamoDB table. You're building a mini catalog with three items: a Wireless Mouse ($29.99), a Mechanical Keyboard ($79.99), and a USB-C Hub ($49.99).
After each command, you should see a response like Created product p001 confirming the item was stored.
Seeing an error instead of a confirmation?
- Double-check that you replaced the URL placeholder with your actual products-api Invoke URL. It should start with https:// and end with .amazonaws.com.
- Make sure the URL ends with /products and not a double slash like //products.
- If you see a 500 error, check that your Lambda function is still deployed by visiting the Lambda console.
Why can't I create products with PowerShell?
Create orders referencing those products
Orders reference products by their ID. This mirrors how real e-commerce systems work. The Orders service stores a productId field but doesn't need to know anything else about the product.
- Create two orders by running these commands (replace the URL with your orders-api Invoke URL):
Invoke-RestMethod -Method PUT -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders" -ContentType "application/json" -Body '{"id": "o001", "productId": "p001", "quantity": 2, "customerName": "Alice"}'
Invoke-RestMethod -Method PUT -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders" -ContentType "application/json" -Body '{"id": "o002", "productId": "p003", "quantity": 1, "customerName": "Bob"}'
What do these orders contain?
Order o001 is from Alice, ordering 2 units of product p001 (Wireless Mouse). Order o002 is from Bob, ordering 1 unit of product p003 (USB-C Hub).
Notice that the Orders service only stores the product ID. It doesn't duplicate the product name or price. Each service owns its own data.
You should see Created order o001 and Created order o002 confirming both orders are stored.
Verify service independence
The defining principle of microservices is autonomy. Each service manages its own data and doesn't depend on the other to function.
You'll prove this by deleting a product and confirming that the Orders service is completely unaware anything changed.
- Retrieve all products and all orders separately to confirm they each operate independently:
Invoke-RestMethod -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products"
Invoke-RestMethod -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders"
You should see all three products (p001, p002, p003) and both orders (o001, o002) returned as JSON.
- Now delete product p002 (Mechanical Keyboard) and then check that all orders still exist:
Invoke-RestMethod -Method DELETE -Uri "[[PRODUCTS_API_URL="your products-api Invoke URL"]]/products/p002"
Invoke-RestMethod -Uri "[[ORDERS_API_URL="your orders-api Invoke URL"]]/orders"
Why does this prove service independence?
In a monolithic application, deleting a product might cascade and remove related orders too. With microservices, each service owns its own database and has no foreign key relationship to the other.
Deleting product p002 from the products table has zero effect on the orders table. The services are autonomous.
After the DELETE command, you should see Deleted product p002. The second command should still return both orders (o001 and o002) unchanged.
Orders disappeared after deleting a product?
This should not happen with the architecture you've built. If your orders are missing, you may have accidentally run the DELETE command against the orders-api URL instead of the products-api URL. Check the URL in your command carefully.
Why are my orders missing after I deleted a product?
Secret mission
Add Inter-Service Communication
Right now, your two microservices are completely independent. The Orders service happily accepts an order for any product ID, even ones that don't exist. In production, services talk to each other to validate data before acting on it. Can you make the Orders service call the Products API to verify a product exists before accepting an order?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on AWS serverless services that fall within the Always Free tier. There are no ongoing costs for leaving these resources idle.
Decide whether to keep your resources running or delete them entirely.
Resources you used:
- 2 DynamoDB tables (products, orders).
- 2 Lambda functions (products-service, orders-service).
- 2 HTTP APIs (products-api, orders-api).
- 2 IAM roles (products-service-role, orders-service-role).
- 2 CloudWatch Log Groups (/aws/lambda/products-service, /aws/lambda/orders-service).
Keep everything running
No action needed. Choose this if you want to keep exploring your microservices or plan to build on them later.
- All resources are within the AWS Always Free tier (1 million Lambda requests/month, 25 GB DynamoDB storage, 1 million API Gateway calls/month).
- They sit idle without costing you anything.
Pause - I'll come back to this later
Since all resources are serverless, they only consume resources when invoked. There is nothing to pause.
- Lambda functions only run when called.
- DynamoDB tables only incur charges when storage exceeds 25 GB (this project uses a few KB).
- API Gateway HTTP APIs only charge per request.
You can safely leave everything as-is and return to the project later with zero cost.
Delete - I don't want to use this again
Remove all project resources and start fresh. Delete in this order to avoid dependency issues.
Delete both HTTP APIs
- Open the API Gateway console at https://console.aws.amazon.com/apigateway.
- On the APIs page, select products-api.
- Choose Actions, then choose Delete.
- Choose Delete to confirm.
- Repeat for orders-api.
Delete both Lambda functions
- Open the Lambda console at https://console.aws.amazon.com/lambda.
- On the Functions page, select products-service.
- Choose Actions, then Delete.
- Type confirm in the text input field and choose Delete.
- Repeat for orders-service.
Delete both DynamoDB tables
- Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.
- In the left navigation pane, choose Tables.
- Choose the checkbox beside the products table.
- Choose Delete and confirm the deletion.
- Repeat for the orders table.
Delete both IAM roles
- Open the IAM console Roles page at https://console.aws.amazon.com/iam/home#/roles.
- Select products-service-role.
- Choose Delete.
- Enter the role name in the text input field and choose Delete.
- Repeat for orders-service-role.
Delete both CloudWatch log groups
- Open the CloudWatch Log groups page at https://console.aws.amazon.com/cloudwatch/home#logs:.
- Select the log group /aws/lambda/products-service.
- Choose Actions, then Delete log group(s).
- In the confirmation dialog, choose Delete.
- Repeat for /aws/lambda/orders-service.
That's a Wrap!
That's a Wrap!
Nice work! You just built a production-style serverless microservices backend on AWS with two fully independent services communicating over live HTTP endpoints.
You've learned how to:
- Build two independent serverless microservices (Products and Orders) each with their own DynamoDB table, Lambda function, and API Gateway HTTP API.
- Write Python Lambda functions handling full CRUD operations via boto3, including routing logic based on HTTP method and path.
- Test and prove service independence end-to-end using PowerShell, demonstrating that changes in one microservice do not affect the other.
- Secret Mission: Added inter-service communication where the Orders service calls the Products API to validate a product exists before accepting an order.
Ready to quiz yourself?