Serverless applications
Build and deploy a small serverless API on AWS Lambda with the SAM CLI: write the handler, test it locally, deploy, read logs and clean up. Learn cold starts, statelessness and when serverless is the wrong choice.
In this tutorial you will build and deploy a tiny serverless API: a GET /menu endpoint for the Brew & Bean coffee shop that runs as an AWS Lambda function behind an HTTP API. You will test it locally, deploy it with the AWS SAM CLI, read its logs, and delete it again so it costs nothing.
Prerequisites: an AWS account (see *AWS core services* for sign-up and IAM basics), the AWS CLI configured with a non-root IAM user or SSO profile, Python 3.12 installed, and the SAM CLI installed (search "install AWS SAM CLI" on the AWS docs). Docker is only needed for sam local, which is optional.
What serverless means
Serverless does not mean there are no servers. It means you do not manage them. You hand the cloud provider your code, and it finds a machine, starts your code when a request arrives, scales it up when many requests arrive, and scales it to zero when nobody is calling.
Think of a tea stall that only hires a helper when a customer arrives and pays the helper only for the minutes worked. A normal server is a helper on a monthly salary who sits idle at 3 am.
The most common serverless pieces are:
Serverless building blocks across clouds
| Piece | What it does | AWS | Azure | Google Cloud |
|---|---|---|---|---|
| Functions (FaaS) | Runs your code per event | Lambda | Azure Functions | Cloud Run functions |
| HTTP front door | Gives the function a public URL, handles routing | API Gateway, Lambda function URL | Built-in HTTP trigger, API Management | Built-in HTTP trigger, API Gateway |
| Managed database | No server to patch, scales on demand | DynamoDB | Cosmos DB | Firestore |
| Object storage | Files and static sites | S3 | Blob Storage | Cloud Storage |
| Queues and events | Triggers and decouples work | SQS, SNS, EventBridge | Service Bus, Event Grid | Pub/Sub |
In this lesson we use AWS Lambda because it is the most widely asked about in interviews. The ideas (event in, response out, pay per use, stateless) are the same on Azure Functions and Google Cloud.
How a Lambda function runs
A Lambda function is a normal function with one required shape: it receives an event (a dictionary describing what happened, such as an HTTP request) and a context (information about the invocation) and returns a result.
When an event arrives and no warm copy of your function is free, Lambda starts a new execution environment: it downloads your code, starts the runtime and runs your initialisation code. This is a cold start and adds delay (from tens of milliseconds to a few seconds depending on runtime, package size and settings). Later requests reuse the warm environment and skip that cost. Code outside the handler runs once per environment, so put expensive set-up (database clients, loading a config file) there.
Key limits to remember (check the official Lambda quotas page for current values): a function has a maximum run time of 15 minutes, memory is configurable and CPU scales with it, and the local disk is temporary. Functions are stateless: never keep user data in a global variable or on local disk and expect it to be there next time.
When serverless is the wrong tool
Lambda is not for everything. Long-running jobs, constant heavy traffic and large in-memory state are usually better on containers or a VM. Serverless shines for spiky, event-driven or low-traffic work.
Step 1: write the function
Create a folder brew-menu and add two files. First the function. The HTTP API sends a v2 payload; we return a dictionary with statusCode, headers and a JSON string body.
import json
MENU = [
{"id": "espresso", "name": "Espresso", "category": "coffee", "price": 120},
{"id": "cappuccino", "name": "Cappuccino", "category": "coffee", "price": 180},
{"id": "cold-brew", "name": "Cold Brew", "category": "coffee", "price": 220},
{"id": "filter-coffee", "name": "Filter Coffee", "category": "coffee", "price": 80},
{"id": "masala-chai", "name": "Masala Chai", "category": "tea", "price": 90},
{"id": "croissant", "name": "Butter Croissant", "category": "bakery", "price": 150},
{"id": "banana-bread", "name": "Banana Bread", "category": "bakery", "price": 130},
{"id": "veg-sandwich", "name": "Veg Grilled Sandwich", "category": "food", "price": 160},
]
def response(status, payload):
return {
"statusCode": status,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(payload),
}
def lambda_handler(event, context):
method = event["requestContext"]["http"]["method"]
path = event["rawPath"]
print(f"request: {method} {path}") # goes to CloudWatch Logs
if method == "GET" and path == "/menu":
category = (event.get("queryStringParameters") or {}).get("category")
items = [m for m in MENU if category is None or m["category"] == category]
return response(200, items)
return response(404, {"error": "not found"})print output becomes log lines in CloudWatch Logs. queryStringParameters is missing (None) when there is no query string, so we default to an empty dict.
Next, the template. SAM (Serverless Application Model) is a short way of writing CloudFormation. It says: one function, and an HTTP API route that triggers it.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Brew and Bean menu API
Resources:
MenuFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.lambda_handler
Runtime: python3.12
CodeUri: .
MemorySize: 128
Timeout: 5
Events:
GetMenu:
Type: HttpApi
Properties:
Path: /menu
Method: get
Outputs:
MenuUrl:
Description: Public URL of the menu endpoint
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/menu"Handler: app.lambda_handler means file app.py, function lambda_handler. ServerlessHttpApi is the HTTP API that SAM creates automatically. A 5 second timeout stops a bug from running (and billing) for long.
Step 2: test it before deploying
You can test the handler without AWS by calling it from Python with a fake event. This is the fastest feedback loop and works without Docker.
from app import lambda_handler
event = {
"rawPath": "/menu",
"requestContext": {"http": {"method": "GET"}},
"queryStringParameters": {"category": "bakery"},
}
result = lambda_handler(event, None)
print(result["statusCode"])
print(result["body"])Run it with python try_local.py inside the brew-menu folder.
request: GET /menu
200
[{"id": "croissant", "name": "Butter Croissant", "category": "bakery", "price": 150}, {"id": "banana-bread", "name": "Banana Bread", "category": "bakery", "price": 130}]If you have Docker running, sam local start-api starts the API on your machine at http://127.0.0.1:3000/menu inside a container that imitates Lambda.
Step 3: deploy with SAM
Deploy
- 1Open a terminal in the
brew-menufolder and confirm your AWS identity withaws sts get-caller-identity. It should show your IAM user or role, not the root user. - 2Run
sam build. It packages your code into a.aws-samfolder. - 3Run
sam deploy --guided. Give the stack a name such asbrew-menu, choose a region near you (for India, Mumbaiap-south-1is the closest), accept the defaults, and allow SAM to create the IAM roles it needs. It asks whether to save your answers tosamconfig.toml: say yes. - 4When it finishes, copy the
MenuUrlvalue from the Outputs section and open it in a browser, or call it withcurl.
sam build
sam deploy --guided
curl "<MenuUrl from the Outputs>?category=coffee"Replace the placeholder with the URL printed by the deploy. On Windows PowerShell use curl.exe.
[{"id":"espresso", ...}, {"id":"cappuccino", ...}, {"id":"cold-brew", ...}, {"id":"filter-coffee", ...}]Behind the scenes SAM created a CloudFormation stack with a Lambda function, an IAM execution role (so the function can write logs), an HTTP API and the permission for the API to call the function. You wrote about 20 lines of YAML for all that.
Step 4: read the logs
Every print and every error from a Lambda function goes to CloudWatch Logs, in a log group named /aws/lambda/<function name>. You can tail it from the terminal.
sam logs --stack-name brew-menu --tailCall the URL again from another terminal and you will see the request: GET /menu line plus START, END and REPORT lines. REPORT shows duration, memory used and billed duration. On the first call after a pause it also shows Init Duration, which is the cold start.
Events beyond HTTP
HTTP is only one trigger. Lambda can also run when a file lands in S3, a message arrives in a queue, a row changes in DynamoDB or a schedule fires. Examples for Brew & Bean:
A new product photo is uploaded to S3, and a function creates a thumbnail. Every night at 11 pm a scheduled function emails the day's sales. An order is placed, a message goes to SQS, and a function sends the confirmation SMS, so a slow SMS provider never slows down the customer.
In every case the pattern is the same: an event arrives, your function handles it and returns.
Design for retries
For queues, make your function idempotent: running it twice with the same message must not cause two SMS messages or two charges. Queue services deliver at least once, so duplicates can happen.
Cost model in one paragraph
Lambda bills for the number of requests and for the time your code runs multiplied by the memory you configured (GB-seconds). The AWS free offering has changed over time, and new accounts now start on a free plan with credits, so check the official AWS Free Tier and Lambda pricing pages for current details before relying on them. Two habits keep a learning project cheap: set a short timeout and delete the stack when done. See *Cloud costs, free tiers and avoiding surprise bills* for budgets and alerts.
Step 5: clean up
sam delete --stack-name brew-menuDeletes the whole CloudFormation stack: function, API and role. If the CloudWatch log group is still listed afterwards, delete it in the CloudWatch console.
Secrets and open endpoints
Never put API keys or database passwords in template.yaml or in code that you commit. Use AWS Systems Manager Parameter Store or Secrets Manager and give the function's role permission to read only that secret. Also remember that a public HTTP API can be called by anyone, so add authentication and throttling before real users arrive.
How this comes up in interviews
Typical questions: What is a cold start and how do you reduce it? (smaller packages, lighter runtimes, less work outside the handler, provisioned concurrency for latency-critical paths.) When would you not use Lambda? How do you handle state in a stateless function? (an external store such as DynamoDB, S3 or Redis.) What happens if a function fails when triggered by a queue? (the message becomes visible again and is retried, and after several failures can go to a dead-letter queue.)
Be ready to describe one serverless thing you actually deployed. This lesson gives you one.
Try it: Add an endpoint
Extend app.py with GET /menu/{id} that returns one item or a 404 JSON error. In template.yaml add a second event with Path: /menu/{id}. Test it with try_local.py first, then redeploy with sam build and sam deploy.
Key takeaways
- Serverless means you do not manage servers: you deploy functions and pay for use, and the platform scales them, even to zero.
- A Lambda handler takes
(event, context)and returns a response; for HTTP the response needsstatusCode,headersand a stringbody. - Functions are stateless. Keep state in a database or storage, and put expensive set-up outside the handler to reuse it on warm starts.
- Cold starts add delay on the first request to a new environment; keep packages small to reduce them.
- SAM lets you describe the function and its API in a short template;
sam build,sam deployandsam deletemanage the lifecycle. - Always set a timeout, protect public endpoints, never hard-code secrets, and delete practice stacks.
Check yourself
Finishing the quiz marks the lesson as done.
1. What happens to the data you store in a global variable inside a Lambda function?
2. What is a cold start?
3. In the SAM template, what does Handler: app.lambda_handler mean?
4. Why should a queue-triggered function be idempotent?
5. Which workload is usually a poor fit for Lambda?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.