This repository provides production-grade multi-stack DevOps implementation integrating:
- AWS Serverless architecture (Lambda, API Gateway, CloudFormation)
- End-to-end CI/CD automation via GitHub Actions with Infrastructure as Code validation
- Cross-platform scripting and observability with Node.js, Python, and AWS Powertools
The goal of this project is to establish a scalable, secure, and maintainable DevOps environment that aligns with enterprise-grade delivery standards and cross-cloud best practices, combining AWS automation within a unified workflow.
Foundation source – AWS CloudFormation template of a sample API with basic request validation.
Built basic REST API у API Gateway (REST API) with request validation through Models and RequestValidators in CloudFormation. On this stage without Lambda - integration HTTP_PROXY based on PetStore demo.
Intent - showcase backend input control and pure IaC implementation.
- Official example used: AWS - API Gateway request validation sample (CloudFormation).
- Key elements reproduced: model for body
POST, validator forGET(query) andPOST(body), resource/validation,DeploymentіStage. - Added custom tests and screenshots.
Resource: /validation
- GET /validation?q1=... - mandatory query-parameter
q1(validation at the level API GW) - POST /validation - перевірка JSON-body by Model (required fields, ranges)
- Integration:
HTTP_PROXY→ PetStore (Lambda will implement on Phase 02) - IaC: one CFN-template
aws-cloudformation/apigw-request-validation.yaml
- aws-cloudformation/apigw-request-validation.yaml
- docs/screenshots/*.jpg
cfn-lint aws-cloudformation/apigw-request-validation.yamlaws cloudformation deploy `
--template-file aws-cloudformation/apigw-request-validation.yaml `
--stack-name req-validators-sample `
--capabilities CAPABILITY_IAM `
--parameter-overrides StageName=v1After creating the stack get an Invoke URL from Outputs:
aws cloudformation describe-stacks --stack-name req-validators-sample `
--query "Stacks[0].Outputs[?OutputKey=='ApiRootUrl'].OutputValue" --output textGET - parameter validation
# 200 OK - parameter present
curl.exe "$env:API_ROOT/validation?q1=dog"# 400 Bad Request - parameter missing
curl.exe "$env:API_ROOT/validation"# 400 Bad Request - empty parameter
curl.exe "$env:API_ROOT/validation?q1="POST - JSON body validation
# 200 OK - valid body
curl.exe -X POST "$env:API_ROOT/validation" `
-H "Content-Type: application/json" `
-d '{"type":"dog","name":"Buddy","price":100,"id":123}'# 400 Bad Request - missing mandatory 'price'
curl.exe -X POST "$env:API_ROOT/validation" `
-H "Content-Type: application/json" `
-d '{"type":"dog","name":"Buddy","id":123}'# 400 Bad Request - 'price' out of model range (min 25)
curl.exe -X POST "$env:API_ROOT/validation" `
-H "Content-Type: application/json" `
-d '{"type":"dog","name":"Buddy","price":10,"id":123}'aws cloudformation delete-stack --stack-name req-validators-sample
aws cloudformation wait stack-delete-complete --stack-name req-validators-sampleSecond Increment: delivered the execution to the AWS Lambda and built a minimal REST /tickets (GET/POST) through API Gateway (REST API) with integration AWS_PROXY. At the input saved the validation of the body for POST (Model + RequestValidator).
The goal is to get a working endpoint that accepts and returns JSON without intermediate stubs.
What exactly has been implemented
GET /tickets-> Lambda getTicket-a2 (demo list).POST /tickets-> Lambda createTicket-a2 (takes{ "title": "...", "priority": ... }, returns createdticket).- Validation (POST): JSON Schema Model with mandatory
title. - CORS: preflight
OPTIONSon the resource and CORS headers in Lambda responses. - IaC: one SAM/CFN-template з API, Method, Model/Validator, Deployment/Stage, Lambda Permissions.
Files
infrastructure/cloudformation/template.yaml- main SAM/CFN-template.src/handlers/getTicket/index.js- handler for GET.src/handlers/createTicket/index.js- handler for POST.aws-cloudformation/packaged.yaml- exitcloudformation package.
S3 bucket for artifacts already created in the previous step.
aws cloudformation package `
--template-file infrastructure/cloudformation/template.yaml `
--s3-bucket serhii-saas-devops-artifacts-eu-west-1 `
--output-template-file aws-cloudformation/packaged.yamlaws cloudformation deploy `
--template-file aws-cloudformation/packaged.yaml `
--stack-name tickets-api-a2 `
--capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND `
--parameter-overrides ApiName=tickets-api StageName=v1$env:TICKETS_URL = (aws cloudformation describe-stacks --stack-name tickets-api-a2 `
--query "Stacks[0].Outputs[?OutputKey=='TicketsInvokeUrl'].OutputValue" --output text)
$env:TICKETS_URLInvoke-RestMethod -Uri $env:TICKETS_URL -Method GET$body = @{ title = "New ticket from A2"; priority = "HIGH" } | ConvertTo-Json
Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST -ContentType "application/json" -Body $body$body = @{ title = ""; priority = "LOW" } | ConvertTo-Json
try {
Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST -ContentType "application/json" -Body $body
} catch { $_.Exception.Response.StatusCode.value__ } curl.exe -i -X OPTIONS "$env:TICKETS_URL"aws cloudformation delete-stack --stack-name tickets-api-a2
aws cloudformation wait stack-delete-complete --stack-name tickets-api-a2To align local development with the AWS Lambda runtime, several Node.js adjustments were introduced during Phase 03.
Each Lambda function (getTicket, createTicket) now contains its own package.json, package-lock.json and node_modules folder.
This mirrors AWS packaging logic and ensures correct deployment with CloudFormation.
A jsconfig.json file was added to define a pure Node/Lambda environment and remove DOM type conflicts in VS Code. This prevents false event warnings and enables IntelliSense for AWS types.
{ "compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"types": ["node", "aws-lambda"] } }After restarting the TypeScript server, all module and type reconfigured, no warnings in environment. The project now matches a production-grade Node.js Lambda structure, according with best practices for AWS.
Intent - raise the minimum REST from the previous phase to the production-baseline: input validation (GET/POST), correct CORS, access control via API Key + Usage Plan, minimum IAM rights, observability (AWS Lambda Powertools + X-Ray), managed log retention, throttling on API Gateway, contract in the form of OpenAPI.
What added compared to Phase 02
- Validation:
GET /tickets- mandatory?limit=throughRequestValidator+RequestParameters;POST /tickets- modelTicketwithtitleas required іpriorityas {LOW, MEDIUM, HIGH}. - CORS:
OPTIONSon the resource + consistent headers in Lambda responses (includingx-api-key). - Security: least-privilege ролі Lambda (
AWSLambdaBasicExecutionRole,AWSXRayDaemonWriteAccess),ApiKey + UsagePlan(key is required forPOST). - Observability:
@aws-lambda-powertools/loggerandmetricsin both function;Tracing: Active(X-Ray). - Performance/Scale: memory tuning (
get:128MB/5s,post:256MB/6s), throttling on Stage (rate/burst). - Ergonomics: export OpenAPI (OAS 3.0) with API Gateway.
Modified files
infrastructure/cloudformation/template.yaml- Validators/Model, CORS, API Key+Usage Plan, throttling, Log Retention, Tracing.src/handlers/getTicket/index.js- Powertools, readinglimit, metricsTicketsListed, CORS-headings.src/handlers/createTicket/index.js- Powertools, validation in code, metricsTicketCreated, CORS-headings.
aws cloudformation package `
--template-file infrastructure/cloudformation/template.yaml `
--s3-bucket serhii-saas-devops-artifacts-eu-west-1 `
--output-template-file aws-cloudformation/packaged.yamlaws cloudformation deploy `
--template-file aws-cloudformation/packaged.yaml `
--stack-name tickets-a3 `
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM `
--parameter-overrides ApiName=tickets-api StageName=v1 ApiKeyValue=dev-key-a3-001-1550da9fdf67 `
--s3-bucket serhii-saas-devops-artifacts-eu-west-1After deploying, check the Outputs of the stack:
aws cloudformation describe-stacks --stack-name tickets-a3 --query "Stacks[0].Outputs" --output tableInvoke-RestMethod -Uri ($env:TICKETS_URL + "?limit=2") -Method GETcurl.exe $env:TICKETS_URL$body = @{ title = "A3 ticket"; priority = "MEDIUM" } | ConvertTo-Json
Invoke-RestMethod -Uri $env:TICKETS_URL -Method POST `
-Headers @{ "x-api-key" = $env:API_KEY; "Content-Type" = "application/json" } `
-Body $body$body = @{ title = ""; priority = "LOW" } | ConvertTo-Json
Invoke-RestMethod -Uri $TICKETS_URL -Method POST `
-Headers @{ "x-api-key" = $API_KEY; "Content-Type" = "application/json" } `
-Body $bodycurl.exe -i -X OPTIONS $env:TICKETS_URLmkdir docs\openapi -Force | Out-Null
aws apigateway get-export `
--rest-api-id $env:REST_ID `
--stage-name v1 `
--export-type oas30 `
--parameters extensions=integrations `
--accept application/json docs/openapi/tickets-oas30-v1.jsonaws cloudformation delete-stack --stack-name tickets-a3
aws cloudformation wait stack-delete-complete --stack-name tickets-a3
























