You tick a todo as done · Lambda + RDS MySQL
12 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:
- VPC: Virtual private cloud: your own private network inside AWS. Nothing gets in or out except the ways you allow. contains:
- Public subnet · to IGW: A slice of the VPC that can reach the internet directly, through the internet gateway (IGW). contains:
- NAT gateway (one-way exit): Lets things in a private subnet start connections out to the internet, while nothing outside can start one in.
- Private subnet · to NAT: A slice of the VPC with no door to the internet. Things here can reach out through the NAT gateway, but nothing outside can reach in. contains:
- ENI (function's network card): Elastic network interface: a virtual network card. It gives the function an address inside your VPC.
- RDS MySQL (database: todoio): A MySQL database that AWS runs for you, including the server, patches and backups. todoio is the database's name.
- Internet gateway (the VPC's door): The door between a VPC and the internet. Without one, nothing in the VPC can talk to the internet directly.
- Public subnet · to IGW: A slice of the VPC that can reach the internet directly, through the internet gateway (IGW). 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.
- VPC: Virtual private cloud: your own private network inside AWS. Nothing gets in or out except the ways you allow. contains:
- Your browser (on the internet)
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 tick a todo as done. Path: Your browser. Your browser sends PATCH /todos/42 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.
- Step into the VPC. Path: Lambda function → ENI. The database lives in a private subnet of a VPC, so the function is attached to that VPC. AWS gives it an ENI (a network card) in the private subnet.
- Ask the database. Path: ENI → RDS MySQL. The app sends SQL to RDS MySQL in the private subnet on port 3306. It logs in with a database username and password, not with IAM. Checks: security-group: todo-db-sg: TCP 3306 from todo-lambda-sg → allow; mysql: MySQL user todo_app: UPDATE on todoio.todos → allow.
- The database answers. Path: RDS MySQL → ENI → Lambda function. MySQL says: 1 row changed.
- Tell your other tabs. Path: Lambda function → ENI → NAT gateway → Internet gateway → API Gateway. The app looks up your connection id in the database and posts a message to it through the WebSocket API. It's attached to the VPC with no public IP, so it gets out through a NAT gateway and the internet gateway. 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 200 OK, and the todo shows a tick.
- 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
PATCH /todos/42over HTTPS.Show me the real thing
The HTTP request
PATCH /todos/42 HTTP/1.1 Host: abc123.execute-api.us-east-1.amazonaws.com Cookie: session=eyJ1c2VySWQiOiJ1LTEyMyJ9.Zm9v Content-Type: application/json {"done":true}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 database lives in a private subnet of a VPC, so the function is attached to that VPC. AWS gives it an ENI (a network card) in the private subnet.
Exam tipA Lambda ENI never gets a public IP, so a VPC-attached function can't reach the internet, even from a public subnet. It needs a NAT gateway. Classic exam trap.
The app sends SQL to RDS MySQL in the private subnet on port 3306. It logs in with a database username and password, not with IAM.
- 🛡Security group todo-db-sg: TCP 3306 from todo-lambda-sgAllowed
- 🗄MySQL MySQL user todo_app: UPDATE on todoio.todosAllowed
Show me the real thing
The SQL (real code sends the values as parameters, never pasted into the string)
UPDATE todos SET done = true WHERE id = 42 AND user_id = 'u-123';Exam tipA security group rule can use another security group as its source: "anything wearing todo-lambda-sg may connect". Exam favourite.
MySQL says: 1 row changed.
The app looks up your connection id in the database and posts a message to it through the WebSocket API. It's attached to the VPC with no public IP, so it gets out through a NAT gateway and the internet gateway.
- 🔑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.completed","todoId":"42"}Exam tipA Lambda attached to a VPC needs a NAT gateway (or a VPC endpoint) to reach anything outside the VPC, even AWS services like SQS.
Every Todo.io tab you have open, on any device, shows the change without a refresh.
You get
200 OK, and the todo shows a tick.Show me the real thing
The HTTP response
HTTP/1.1 200 OKLambda 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 PATCH /todos/42 user=u-123 status=200 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 never checked your SQL. MySQL has its own users; IAM only manages the database server itself.
- The database sits in a private subnet with no route to the internet. Only the security groups you allowed can reach it.
- The database password lives in the app config here. In production it belongs in Secrets Manager (Level 1).
- To reach a database in a VPC, the function had to be attached to that VPC.
- Every Lambda copy opens its own MySQL connection, and a traffic spike can exhaust them. RDS Proxy pools connections (Level 2).
- A WebSocket stays open, so the server side can send you updates without you asking.
- A Lambda in a VPC can't reach public AWS services by itself, so this VPC needed a NAT gateway.
- You, the customer, were never an IAM user. IAM controls the app, not its customers.