cerf
Serverless REST API pattern

Serverless REST API pattern

The serverless REST API: API Gateway in front, Lambda in the middle, DynamoDB behind

Three managed services, no servers to patch, and a request path that scales from zero to spike without you touching a load balancer.

The shape of the pattern

This is the reference design that shows up whenever a developer question says 'build a REST API without managing servers.' Three fully managed services do the work: Amazon API Gateway is the front door that terminates HTTPS and exposes the REST resources and methods; AWS Lambda is the compute that runs your handler code; Amazon DynamoDB is the data store that holds the items.

What makes it 'serverless' is not that servers vanish, but that you never provision, patch, or scale them. API Gateway absorbs the incoming requests, Lambda spins up an execution environment per concurrent request, and DynamoDB partitions the data behind a managed key-value engine. You pay per request and per millisecond of compute, and at idle the bill trends toward zero because there is no always-on fleet.

The serverless topology: three managed services, no fleet
The serverless topology: three managed services, no fleetCallersAWS Cloud — no fleet to patch or scaleus-east-1Front door — terminates HTTPS, authorizesCompute — per-request, statelessData & durable stateObservabilityHTTPSHTTPSTLS terminationauthorize JWTinvokeassumesread / write itemslogs + tracesWeb clientbrowserMobile clientappAPI GatewayREST API front doorACMTLS certificateCognitouser poolLambdahandler computeExecution roleIAMDynamoDBitems / durable sta…CloudWatchlogs, metrics, X-Ray
Trace
The pattern is three fully managed tiers inside one Region. API Gateway is the front door that terminates HTTPS (with an ACM certificate) and authorizes callers against a Cognito user pool. Lambda is per-request, stateless compute that assumes an IAM execution role. DynamoDB holds the durable state, and CloudWatch collects logs, metrics, and X-Ray traces. Nothing here is an always-on server you provision, patch, or scale by hand, so at idle the bill trends toward zero.

How a request flows

A client sends an HTTPS request with a bearer token to the API Gateway endpoint. API Gateway validates the token against an Amazon Cognito user pool before the request reaches any of your code, so unauthenticated callers are rejected at the edge.

Once authorized, API Gateway uses a Lambda proxy integration: it packages the entire request (path, query string, headers, and body) into a single event object and invokes the function. The handler returns an object containing statusCode, headers, and body, and API Gateway maps that straight back to the caller as the HTTP response. Inside the handler, the code reads and writes DynamoDB using the partition key, and emits logs and traces to CloudWatch and X-Ray. The whole path is synchronous: the client waits while Lambda runs and DynamoDB responds.

How one request flows: proxy integration in and structured response out
How one request flows: proxy integration in and structured response outAWS Cloudus-east-11. HTTPS + bearer token2. validate JWT3. proxy event {path,query,headers,body}assumes role4. GetItem / PutItem by PKitems5. {statusCode, headers, body}6. HTTP responselogstrace segmentsClient appweb / mobileAPI GatewayREST methodCognitoauthorizeruser pool / JWTLambda proxyhandlerstatelessExecution roleIAMDynamoDBpartition key accessCloudWatch Logshandler logsX-Raytrace segments
Trace
A single synchronous round trip. The client sends HTTPS with a bearer token; API Gateway validates the token against the Cognito user pool before any of your code runs, then uses a Lambda proxy integration to pass the entire request as one event object. The handler assumes its execution role, reads or writes DynamoDB by partition key, and returns a {statusCode, headers, body} object that API Gateway maps straight back to the caller. Logs and X-Ray traces stream to CloudWatch. A malformed body from a proxy handler is what turns into a 502 at the gateway.

Stateless, concurrent, and idempotent

Lambda is stateless. Anything you write to the function's local disk or to a module-level variable may or may not survive to the next invocation, because a request can land on a brand-new execution environment at any time. So each invocation must carry everything it needs, and durable state belongs in DynamoDB, not in the function.

Concurrency scales per request: two hundred simultaneous callers means up to two hundred execution environments running in parallel, each handling exactly one request. That elasticity is why the pattern rides traffic spikes, but it also means a client (or an internal retry) can send the same request twice. The defense is idempotency: use a DynamoDB conditional write (a ConditionExpression) so that a retried 'create order' does not produce a second order, and design handlers so that repeating an operation yields the same result.

Stateless scale-out and idempotent retries
Stateless scale-out and idempotent retriesAWS Cloudus-east-1Lambda concurrency — one env per request, nothing sharedN requests in parallel1 request : 1 envduplicate retry (same key)scale outscale outPutItem if attribute_not_exists(orderId) -> 201same orderId -> ConditionalCheckFailedread / write by PKread / write by PKemit metricsConcurrentcallerse.g. 200 at onceAPI GatewayREST front doorEnv Ahandles create-orde…Env Bhandles retry of XEnv Ccold startEnv None per requestOrders tablePK = orderId, Condi…CloudWatchConcurrentExecutions
Trace
Concurrency scales per request: N simultaneous callers means up to N execution environments running in parallel, each handling exactly one request, some of them brand-new cold starts. Nothing on local disk or in module-level variables is shared between them, so durable state lives only in DynamoDB. Because a client or an internal retry can send the same 'create order' twice, the write is guarded by a ConditionExpression: the first PutItem with attribute_not_exists(orderId) wins with a 201, and the duplicate is rejected with ConditionalCheckFailedException instead of creating a second order.

What this teaches for the exam

Watch for the proxy-versus-non-proxy distinction: with a Lambda proxy integration the function receives the raw request and must return the statusCode/headers/body shape itself, whereas a non-proxy integration uses mapping templates to transform requests and responses. Questions that mention returning a malformed body from a proxy handler are pointing at a 502 from API Gateway.

At the data layer, remember query versus scan: a Query targets a single partition key and reads only matching items, while a Scan reads every item in the table and is the answer to 'why is this API slow and expensive.' Choose a high-cardinality partition key so access spreads evenly. And when the client should not wait, the synchronous chain is wrong: put a queue or an asynchronous invoke with a dead-letter queue between the layers so failed work is retried and captured instead of lost. Finally, the Lambda execution role, not the caller's credentials, is what grants the function permission to touch DynamoDB, and it should be scoped to least privilege.

When the client should not wait: decouple with a queue and a dead-letter queue
When the client should not wait: decouple with a queue and a dead-letter queueAWS Cloudus-east-1Synchronous front — returns fastAsynchronous processing — client does not waitData & identityObservability1. HTTPS requestproxy integration2. SendMessage3. 202 body4. 202 Accepted5. event source mappingassumes6. Query by PK (not Scan)on repeated failurelogs + metricsalarm on depthClientdoes not waitAPI Gatewayproxy integrationFront handlerreturns 202 fastBuffer queueSQSWorkerasync processorDead-letterqueuecaptured failuresDynamoDBQuery by PK, not Sc…Execution roleleast privilegeCloudWatchlogs + DLQ alarm
Trace
The exam trap: the synchronous chain is wrong when the client should not wait for slow or failure-prone work. The fix is to put a queue (or an asynchronous invoke) between the layers. The front handler accepts the request, enqueues it, and returns 202 immediately; a worker Lambda drains the queue asynchronously, assumes a least-privilege execution role, and Queries DynamoDB by partition key rather than running an expensive Scan. Messages that keep failing land in a dead-letter queue so the work is captured and retried instead of lost, with CloudWatch alarming on DLQ depth.

Sources

Practice what you just read

6 questions from this architecture

Loading…