Skip to main content

Command Palette

Search for a command to run...

Building a Production-Style Serverless Order Management System on AWS

Updated
9 min read
K
I am currently building my career in Cloud and DevOps by working on real world projects from scratch. Instead of only watching tutorials, I focus on hands on learning by designing, building, and deploying complete systems on AWS. My goal is to understand not just how tools work, but how real world cloud architectures are structured and how requests flow through them. I have recently deployed my first production-style static website using S3, CloudFront, Route 53, ACM, and WAF with a custom domain. Through this blog, I document my learning journey in a simple and practical way so that others who are starting out can understand complex concepts without confusion. I am continuously working on improving my skills and will be building more advanced projects involving networking, infrastructure, automation, and real-world DevOps practices. This is just the beginning.

When I started this project, my goal was not just to build another basic CRUD API.

I wanted to understand how a real cloud backend is designed when we care about scalability, security, monitoring, cost, and maintainability.

So I built a Serverless Order Management System on AWS using:

  • Amazon API Gateway

  • AWS Lambda

  • Amazon DynamoDB

  • Amazon Cognito

  • IAM

  • Amazon CloudWatch

  • Amazon SNS

  • API Gateway throttling

  • Postman for API testing

GitHub Repository: Serverless Order Management System
Portfolio: kamleshcloud.com
LinkedIn: Kamlesh Dubale

What I Built

This project is a serverless backend for managing orders.

It supports:

  • Creating an order

  • Getting all orders

  • Getting a specific order by ID

  • Deleting an order

  • Protecting API routes using Cognito authentication

  • Monitoring Lambda errors with CloudWatch

  • Sending alerts using SNS

  • Restricting Lambda permissions using least-privilege IAM

  • Protecting the API from excessive traffic using throttling

The final architecture is fully serverless, meaning there are no EC2 instances or servers to manage.

High-Level Architecture

The request flow looks like this:

User / Postman
    ↓
Amazon Cognito
    ↓
Amazon API Gateway
    ↓
AWS Lambda
    ↓
Amazon DynamoDB
    ↓
CloudWatch Logs / Alarms
    ↓
SNS Email Alerts

API Gateway acts as the public entry point.

Lambda handles the backend logic.

DynamoDB stores the order records.

Cognito adds authentication.

CloudWatch and SNS provide monitoring and alerting.

IAM controls what Lambda is allowed to access.

API throttling protects the system from excessive traffic.

Why I Chose Serverless

Traditional backend systems usually require servers.

That means someone has to think about:

  • Operating system updates

  • Server patching

  • Scaling servers

  • Load balancing

  • Capacity planning

  • Availability

  • Runtime maintenance

With serverless, AWS handles most of that.

Lambda only runs when a request comes in. DynamoDB scales automatically. API Gateway gives a managed HTTPS endpoint. Cognito manages authentication. CloudWatch collects logs and metrics.

This makes serverless a very good fit for event-driven APIs, small backend services, prototypes, internal tools, and scalable cloud-native applications.

API Gateway Routes

I created four API routes:

Method Route Purpose
POST /orders Create a new order
GET /orders List all orders
GET /orders/{id} Get one order by ID
DELETE /orders/{id} Delete one order

This route structure follows a simple REST-style API design.

For example:

POST /orders

means create an order.

GET /orders

means list all orders.

GET /orders/{id}

means retrieve one specific order.

DELETE /orders/{id}

means delete one specific order.

Lambda Functions

Instead of using one large Lambda function for everything, I created separate Lambda functions:

Lambda Function

Responsibility

CreateOrderFunction

Creates a new order

GetOrderFunction

Gets one order

ListOrdersFunction

Lists all orders

DeleteOrderFunction

Deletes an order

This follows the single responsibility principle.

Each function has one clear job.

This makes the system easier to debug, easier to secure, and easier to explain.

DynamoDB Table Design

The DynamoDB table is called:

orders

The partition key is:

OrderId

Each order item contains:

{
  "OrderId": "994dd3bc-a43a-4342-b07b-3f5829e3c046",
  "customerName": "Kamlesh",
  "product": "MacBook Pro",
  "quantity": 1
}

I used OrderId as the partition key because every order needs a unique identifier.

This allows DynamoDB to retrieve a specific order efficiently using GetItem.

Create Order Flow

When a client sends this request:

POST /orders

with this JSON body:

{
  "customerName": "Kamlesh",
  "product": "MacBook Pro",
  "quantity": 1
}

the flow is:

Postman
    ↓
API Gateway
    ↓
CreateOrderFunction
    ↓
DynamoDB PutItem
    ↓
Order saved

The Lambda function generates a unique OrderId using Python’s uuid module.

Then it stores the item in DynamoDB using boto3.

Input Validation

A backend API should not blindly accept bad input.

So I added validation for required fields:

customerName
product
quantity

If the request body is missing required fields, the API returns a proper error response instead of crashing.

Example bad request:

{
  "customerName": "Kamlesh"
}

Response:

{
  "message": "Missing required fields",
  "missingFields": ["product", "quantity"],
  "requiredFields": ["customerName", "product", "quantity"]
}

This is important because production APIs should give clear and useful error messages.

Get All Orders

The GET /orders endpoint lists all orders from DynamoDB.

The response looked like this:

[
  {
    "product": "MacBook Pro",
    "quantity": 1,
    "customerName": "Kamlesh",
    "OrderId": "994dd3bc-a43a-4342-b07b-3f5829e3c046"
  }
]

For this project, I used DynamoDB Scan.

In a real high-scale production system, I would avoid unnecessary scans and design access patterns carefully using partition keys, sort keys, and indexes.

Get Order by ID

The GET /orders/{id} endpoint uses a path parameter.

Example:

GET /orders/994dd3bc-a43a-4342-b07b-3f5829e3c046

API Gateway passes the ID to Lambda inside:

event["pathParameters"]["id"]

Lambda then uses DynamoDB GetItem to retrieve the order.

This helped me understand how API Gateway converts HTTP requests into Lambda event objects.

Delete Order

The DELETE /orders/{id} endpoint deletes an order from DynamoDB.

The flow is:

Client
    ↓
API Gateway
    ↓
DeleteOrderFunction
    ↓
DynamoDB DeleteItem
    ↓
Deletion confirmation

This completed the main CRUD functionality of the project.

Cognito Authentication

After the CRUD APIs were working, I added Amazon Cognito.

The goal was to protect sensitive API routes.

Without authentication, anyone who knows the API URL can send requests.

That is not acceptable for a real backend.

So I created:

  • Cognito User Pool

  • App Client

  • Cognito Hosted Login Page

  • JWT Authorizer in API Gateway

When an unauthenticated request is sent to the protected POST /orders route, the API returns:

{
  "message": "Unauthorized"
}

This proves that API Gateway is rejecting anonymous requests before they reach Lambda.

That is a strong security boundary.

IAM Least Privilege

During development, I temporarily used broader DynamoDB permissions to move quickly.

But after the project was working, I replaced full DynamoDB access with a custom least-privilege policy.

The Lambda execution role was allowed only these actions:

dynamodb:PutItem
dynamodb:GetItem
dynamodb:DeleteItem
dynamodb:Scan

and only on the orders table.

This is very important.

A Lambda function that only needs to access one table should not have permission to modify every DynamoDB table in the account.

This follows the principle of least privilege.

CloudWatch Monitoring

A system is not production-ready if we cannot observe it.

So I used CloudWatch Logs to inspect Lambda executions and debug issues.

I also created a CloudWatch alarm for Lambda errors.

Alarm name:

CreateOrderFunction-Errors-Alarm

The alarm checks the Lambda Errors metric.

If the function records one or more errors within a one-minute period, the alarm can trigger an action.


SNS Alerting

To make the monitoring useful, I connected the CloudWatch alarm to Amazon SNS.

The alerting flow is:

Lambda error
    ↓
CloudWatch metric
    ↓
CloudWatch alarm
    ↓
SNS topic
    ↓
Email notification

This is closer to how real DevOps teams monitor production systems.

It is not enough to only log errors. Someone needs to be notified when something breaks.

API Throttling

I also configured API Gateway throttling.

Settings used:

Burst limit: 50
Rate limit: 100

Throttling protects the API from:

  • accidental request loops

  • basic abuse

  • sudden spikes

  • uncontrolled client traffic

This is an important production control.

Even if the backend is serverless and scalable, we should still control how much traffic can hit the API.

Problems I Faced and Fixed

This project was not smooth from start to finish. I ran into multiple real-world issues.

  1. DynamoDB table name mismatch

My code used:

Orders

but the actual table name was:

orders

DynamoDB table names are case-sensitive.

This caused a ResourceNotFoundException.

  1. Primary key mismatch

The DynamoDB partition key was:

OrderId

but my first Lambda code used:

orderId

That caused a validation error because DynamoDB expected the exact key name.

  1. Decimal serialization issue

DynamoDB returns numbers as Decimal objects in Python.

When returning an item through API Gateway, Python failed with:

Object of type Decimal is not JSON serializable

I fixed this by adding a custom serializer to convert Decimal values before returning the JSON response.

  1. Cognito Unauthorized response

After attaching the Cognito authorizer, my POST /orders request started returning:

{ "message": "Unauthorized" }

At first, this looked like an error.

But it was actually the correct behavior because the route was protected and no JWT token was being sent.

That helped me understand authentication at the API Gateway layer.


Why This Project Matters

This project helped me move beyond simply deploying a Lambda function.

I learned how multiple AWS services work together:

Authentication
    ↓
API routing
    ↓
Business logic
    ↓
Database
    ↓
Monitoring
    ↓
Alerting
    ↓
Security controls

This is the difference between building a demo and designing a real cloud backend.


Final Repository Structure

serverless-order-management-system/
│
├── README.md
├── architecture-diagram.png
│
├── lambda-functions/
│   ├── create_order.py
│   ├── get_order.py
│   ├── list_orders.py
│   └── delete_order.py
│
├── screenshots/
│   ├── API Gateway Routes.png
│   ├── API Throttling.png
│   ├── Architecture diagram.png
│   ├── Cloudwatch Alarm.png
│   ├── Cognito Authorizer.png
│   ├── DynamoDB- Explore Items.png
│   ├── DynamoDB order Items overview.png
│   ├── IAM least privilage role.png
│   ├── Lambda Functions.png
│   ├── POSTMAN - Get all orders.png
│   └── POSTMAN - Unauthorized.png
│
└── docs/
    └── blog-draft.md

What I Would Improve Next

If I continue improving this project, I would add:

  • Terraform for Infrastructure as Code

  • GitHub Actions CI/CD pipeline

  • Separate dev and prod environments

  • Custom domain for API Gateway

  • AWS WAF

  • Full JWT token testing in Postman

  • More detailed CloudWatch dashboards

  • Unit tests for Lambda functions

  • Better DynamoDB access patterns for larger workloads


Final Thoughts

This project gave me practical experience with building a secure, monitored, serverless backend on AWS.

The biggest learning was not just writing Lambda code.

The biggest learning was understanding how cloud components fit together:

  • API Gateway exposes the backend

  • Cognito protects the API

  • Lambda runs the business logic

  • DynamoDB stores the data

  • IAM controls permissions

  • CloudWatch observes the system

  • SNS sends alerts

  • Throttling protects the API

This is the kind of architecture thinking I want to keep building as I continue growing as a Cloud and DevOps Engineer.

GitHub Repository: Serverless Order Management System
Portfolio: kamleshcloud.com
LinkedIn: Kamlesh Dubale

6 views

More from this blog

K

Kamlesh Cloud & DevOps

3 posts

I’m documenting my journey into Cloud and DevOps by building real projects from scratch and sharing everything I learn along the way.

This blog is focused on hands-on AWS projects, system design thinking, and understanding how things actually work behind the scenes — not just theory.

If you're starting your cloud journey, this is a place where complex concepts are broken down into simple, practical explanations.