A complete, production-ready serverless CRUD API on AWS — API Gateway REST, five single-purpose Lambdas, one DynamoDB table. Environment-aware removal policies, least-privilege IAM, and tests for both the handler logic and the infrastructure. TypeScript, AWS CDK v2, Node 22.
Topics: aws-cdk · cdk · serverless · api-gateway · aws-lambda · dynamodb · typescript · crud-api · infrastructure-as-code · least-privilege · nodejs
(CORS)
client ──> API Gateway (REST) ──> Lambda x5 ──> DynamoDB (items)
/items POST -> create-item ─┐
/items GET -> list-items │ one function per operation,
/items/{id} GET -> get-item │ each with its own IAM grant
/items/{id} PUT -> update-item │
/items/{id} DELETE -> delete-item ─┘
One handler per operation, not a single router. Each function stays small and gets exactly the DynamoDB permissions it needs — write grants for create/update/delete, read grants for get/list.
| Path | Purpose |
|---|---|
bin/cdk-serverless-crud.ts |
CDK app entry point |
lib/cdk-serverless-crud-stack.ts |
Table, five Lambdas, REST API, IAM |
lambda/create-item.ts |
POST /items |
lambda/get-item.ts |
GET /items/{id} |
lambda/list-items.ts |
GET /items |
lambda/update-item.ts |
PUT /items/{id} |
lambda/delete-item.ts |
DELETE /items/{id} |
test/handlers.test.ts |
Handler logic, DynamoDB client mocked |
test/stack.test.ts |
Infrastructure assertions on the synthesized template |
- Node.js 22+, AWS credentials configured, CDK bootstrapped once:
npx cdk bootstrap
npm install
npm test # handler + infra tests, no AWS needed
npx cdk diff --context env=dev
npm run deploy -- --context env=devDeploy prints the API base URL — RestApi emits it automatically under a key
like ItemsApiEndpoint...:
CdkServerlessCrudStack.ItemsApiEndpointXXXX = https://XXXX.execute-api.<region>.amazonaws.com/prod/
API=https://XXXX.execute-api.<region>.amazonaws.com/prod
# create
curl -X POST "$API/items" -H 'content-type: application/json' \
-d '{"name":"first item"}'
# {"id":"...","name":"first item","createdAt":"..."}
# list
curl "$API/items"
# get one
curl "$API/items/<id>"
# update
curl -X PUT "$API/items/<id>" -H 'content-type: application/json' \
-d '{"name":"renamed"}'
# delete
curl -X DELETE "$API/items/<id>" -i # 204 No ContentThe --context env= flag switches two production safeguards:
env |
Table removal policy | Point-in-time recovery |
|---|---|---|
dev (default) |
DESTROY |
off |
prod |
RETAIN |
on |
npm run deploy -- --context env=prod # RETAIN + PITR
npm run destroy -- --context env=dev # tears everything down, table includedIn prod the table survives cdk destroy; you delete it manually on purpose.
- Node 22 / ARM is not set here — runtime is
NODEJS_22_Xon the default (x86) architecture to keep the example simple. Switch toArchitecture.ARM_64for a small price/perf win. removalPolicyis context-driven, not hardcoded, so one stack file serves every environment.