You add a todo · Lambda + DynamoDB
13 steps from your browser to the database and back.
Text version of this diagram.
What sits where:
- AWS Region · us-east-1: A Region is a part of the world where AWS has data centers. Most things you build live in one Region. us-east-1 is in northern Virginia. contains:
- API Gateway (HTTP API · front door): The front door for the app's web requests: it receives each one and hands it to your code. An HTTP API is its simple kind for ordinary requests.
- API Gateway (WebSocket API · live line): The front door for the app's web requests. A WebSocket API keeps a connection open, so the server can push updates to your browser instantly.
- CloudWatch Logs (app log lines): Collects the lines your app writes about what it's doing (its logs), so you can search them later.
- Lambda function (runs once per request): Code that AWS runs only when a request arrives. No server to look after, and you pay per request.
- DynamoDB (Todo.io's tables): AWS's serverless NoSQL database: you store and fetch items by key, and it scales by itself with no server to manage.
- Your browser (on the internet)
- IAM (global · who can do what): Identity and Access Management: decides who (and which app) may do what in AWS. It's global, the same in every Region.
Steps, in order:
- The live line is already open. Path: Your browser → API Gateway. When you logged in, your browser opened a WebSocket to API Gateway. It stays open, so AWS can send you messages any time.
- You add a todo. Path: Your browser. Your browser sends POST /todos over HTTPS.
- Into the front door. Path: Your browser → API Gateway. The request arrives at API Gateway, a managed HTTPS front door. There's no server or firewall for you to look after.
- Wake up the function. Path: API Gateway → Lambda function. API Gateway invokes your Lambda function and hands it the request as an event. Checks: iam: Lambda resource policy: API Gateway may invoke todo-api → allow.
- Who are you?. Path: Lambda function. The app checks your signed session cookie and finds your user id, u-123. This is the app's own login, not AWS.
- Get AWS credentials. Path: Lambda function. The app gets temporary credentials for its IAM role (the execution role) which Lambda already put in its environment variables. No keys are stored in the code.
- Call DynamoDB. Path: Lambda function → DynamoDB. The function calls PutItem on the DynamoDB API over HTTPS, signed with those credentials.
- Is this allowed?. Path: DynamoDB → IAM. DynamoDB checks with IAM: may todo-app-lambda-role do dynamodb:PutItem here? The policy on the role allows it on the table's ARN. Checks: iam: todo-app-lambda-role: dynamodb:PutItem on table/Todos → allow.
- DynamoDB answers. Path: DynamoDB → Lambda function. DynamoDB confirms the item is saved.
- Tell your other tabs. Path: Lambda function → API Gateway. The app looks up your connection id in the database and posts a message to it through the WebSocket API. Checks: iam: todo-app-lambda-role: execute-api:ManageConnections → allow.
- It shows up instantly. Path: API Gateway → Your browser. Every Todo.io tab you have open, on any device, shows the change without a refresh.
- Back to you. Path: Lambda function → API Gateway → Your browser. You get 201 Created, and "Buy milk" appears in your list.
- Leave a note. Path: Lambda function → CloudWatch Logs. Lambda sends what the function printed to CloudWatch Logs, so Todo.io's engineers can see what happened.
When you logged in, your browser opened a WebSocket to API Gateway. It stays open, so AWS can send you messages any time.
Show me the real thing
The WebSocket handshake
GET /prod HTTP/1.1 Host: ws123.execute-api.us-east-1.amazonaws.com Upgrade: websocket Connection: Upgrade → API Gateway assigns connection id Ab3xYzEXAMPLE=Exam tipAn API Gateway WebSocket API has $connect, $disconnect and $default routes. Each one can call Lambda or an HTTP backend.
Your browser sends
POST /todosover HTTPS.Show me the real thing
The HTTP request
POST /todos HTTP/1.1 Host: abc123.execute-api.us-east-1.amazonaws.com Cookie: session=eyJ1c2VySWQiOiJ1LTEyMyJ9.Zm9v Content-Type: application/json {"title":"Buy milk"}The request arrives at API Gateway, a managed HTTPS front door. There's no server or firewall for you to look after.
API Gateway invokes your Lambda function and hands it the request as an event.
- 🔑IAM Lambda resource policy: API Gateway may invoke todo-apiAllowed
Show me the real thing
Resource-based policy on the function: who may call it
{ "Effect": "Allow", "Principal": { "Service": "apigateway.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:us-east-1:111122223333:function:todo-api", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:execute-api:us-east-1:111122223333:abc123/*" } } }Exam tipThis is a resource-based policy. It lives on the function and says who may call it, the opposite of a policy attached to a role.
The app checks your signed session cookie and finds your user id,
u-123. This is the app's own login, not AWS.The app gets temporary credentials for its IAM role (the execution role) which Lambda already put in its environment variables. No keys are stored in the code.
Show me the real thing
Trust policy on todo-app-lambda-role: who may use this role
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }Exam tipEvery Lambda function has exactly one execution role. Its permissions are everything your code can do in AWS.
The function calls
PutItemon the DynamoDB API over HTTPS, signed with those credentials.Show me the real thing
The PutItem request body
{ "TableName": "Todos", "Item": { "userId": { "S": "u-123" }, "todoId": { "S": "42" }, "title": { "S": "Buy milk" }, "done": { "BOOL": false } } }Exam tipThis function isn't attached to a VPC, so it reaches DynamoDB directly. No VPC, no security groups.
DynamoDB checks with IAM: may
todo-app-lambda-roledodynamodb:PutItemhere? The policy on the role allows it on the table's ARN.- 🔑IAM todo-app-lambda-role: dynamodb:PutItem on table/TodosAllowed
Show me the real thing
Permissions policy on todo-app-lambda-role
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:Query", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem" ], "Resource": [ "arn:aws:dynamodb:us-east-1:111122223333:table/Users", "arn:aws:dynamodb:us-east-1:111122223333:table/Todos" ] } ] }Exam tipIAM starts from deny. A request is only allowed if some policy explicitly allows it and nothing explicitly denies it.
DynamoDB confirms the item is saved.
The app looks up your connection id in the database and posts a message to it through the WebSocket API.
- 🔑IAM todo-app-lambda-role: execute-api:ManageConnectionsAllowed
Show me the real thing
Posting to a connection
POST /prod/@connections/Ab3xYzEXAMPLE= HTTP/1.1 Host: ws123.execute-api.us-east-1.amazonaws.com {"type":"todo.created","todoId":"42"}Exam tipYour browser never asked for this. Anything with permission can push to a connection id through the @connections API.
Every Todo.io tab you have open, on any device, shows the change without a refresh.
You get
201 Created, and "Buy milk" appears in your list.Show me the real thing
The HTTP response
HTTP/1.1 201 CreatedLambda sends what the function printed to CloudWatch Logs, so Todo.io's engineers can see what happened.
Show me the real thing
The log line
2026-10-11T18:42:07Z INFO POST /todos user=u-123 status=201 14msExam tipLambda sends whatever your function prints to CloudWatch Logs automatically, as long as the execution role allows logs:PutLogEvents.
What to notice
- There is no server at all. API Gateway takes the request and Lambda runs your code only when one arrives.
- IAM approved every database call, using a role. There's no database password anywhere.
- DynamoDB isn't in your VPC. It's a public AWS service called over HTTPS.
- A WebSocket stays open, so the server side can send you updates without you asking.
- You, the customer, were never an IAM user. IAM controls the app, not its customers.