Skip to content

shaunniee/AWS_SERVERLESS_BLOG

Repository files navigation

☁️ AWS Serverless Blog Platform (www.sblog.stsproj.com)

A production-style, fully serverless blog platform built entirely on AWS

AWS Terraform Lambda DynamoDB Status

Two frontends. Two APIs. Strict security boundaries. Event-driven by default. 100% Infrastructure as Code.


Table of Contents


🎯 Project Overview

This is a production-style serverless blog platform built end-to-end on AWS β€” from compute and storage to CI/CD and observability. The goal was to design a real-world cloud system that solves the common pitfalls of hobby AWS projects: mixed authentication levels, publicly accessible storage buckets, synchronous blocking for async work, and manually configured resources.

What makes this different from typical demo projects

Common Demo Project Problem How this project solves it
Admin and public APIs share the same Gateway Two completely separate API Gateways with different trust levels
S3 buckets are publicly accessible All buckets private β€” media served exclusively via CloudFront OAC
Security is bolted on Cognito authorizer is enforced at the API Gateway layer, not in Lambda code
Async work (emails, cleanup) blocks user responses Fully decoupled via EventBridge β€” users get instant responses
Single IAM role shared by all functions 16 granular per-function IAM policies (least privilege by design)
Infrastructure manually created in the Console 100% Terraform β€” every resource is codified and version-controlled
No observability X-Ray tracing, CloudWatch alarms, structured logging, and a unified dashboard
Deployments are risky Canary deployments via CodeDeploy with automatic rollback on alarm

πŸ›οΈ Architecture Overview

The platform is split into three clearly separated zones. Failure in one zone does not cascade to the others.

flowchart TD
    classDef userStyle fill:#f97316,stroke:#c2410c,color:#fff
    classDef publicStyle fill:#2563eb,stroke:#1d4ed8,color:#fff
    classDef adminStyle fill:#16a34a,stroke:#15803d,color:#fff
    classDef asyncStyle fill:#7c3aed,stroke:#6d28d9,color:#fff
    classDef dbStyle fill:#475569,stroke:#334155,color:#fff
    classDef busStyle fill:#d97706,stroke:#b45309,color:#fff
    classDef alarmStyle fill:#dc2626,stroke:#b91c1c,color:#fff

    subgraph PUBLIC["🌍  Public Zone"]
        PB(["πŸ‘€ User"]):::userStyle --> PCF["☁️ CloudFront"]:::publicStyle
        PCF --> PS3["πŸ—‚οΈ S3  Β·  React SPA"]:::publicStyle
        PCF --> PAGW["πŸ”“ API Gateway  Β·  No Auth"]:::publicStyle
        PAGW --> PL["⚑ Lambda  ·  Read Only"]:::publicStyle
        PL --> DDB[("πŸ—„οΈ DynamoDB")]:::dbStyle
    end

    subgraph ADMIN["πŸ”  Admin Zone"]
        AB(["πŸ‘€ Admin"]):::userStyle --> ACF["☁️ CloudFront"]:::adminStyle
        ACF --> AS3["πŸ—‚οΈ S3  Β·  React CMS"]:::adminStyle
        ACF --> AAGW["πŸ›‘οΈ API Gateway  Β·  Cognito JWT"]:::adminStyle
        AAGW --> AL["⚑ Lambda  ·  Full CRUD"]:::adminStyle
        AL --> DDB2[("πŸ—„οΈ DynamoDB")]:::dbStyle
        AL --> EB(["🚌 EventBridge Bus"]):::busStyle
    end

    subgraph ASYNC["⚑  Async Zone"]
        NL["πŸ”” Notifications Lambda"]:::asyncStyle --> SES["πŸ“§ SES  Β·  Email"]:::asyncStyle
        CL["🧹 Cleanup Lambda"]:::asyncStyle --> S3M["πŸ—‘οΈ S3  Β·  Delete Media"]:::asyncStyle
        NL -->|retries exhausted| DLQ1["⚠️ SQS DLQ"]:::alarmStyle
        CL -->|retries exhausted| DLQ2["⚠️ SQS DLQ"]:::alarmStyle
        DLQ1 & DLQ2 --> CWA["🚨 CloudWatch Alarm"]:::alarmStyle
    end

    EB -->|LeadCreated| NL
    EB -->|PostDeleted| CL

    style PUBLIC fill:#eff6ff,stroke:#2563eb,color:#1e3a5f
    style ADMIN fill:#f0fdf4,stroke:#16a34a,color:#14532d
    style ASYNC fill:#f5f3ff,stroke:#7c3aed,color:#3b0764
Loading

Architecture Diagram

alt text

AWS X-Ray Trace Map

alt text


☁️ AWS Services Used

Service Role in this Project
AWS Lambda All backend compute β€” 6 single-purpose functions + 1 shared layer
Amazon API Gateway REST API layer β€” 2 separate gateways (public and admin) with throttling
Amazon DynamoDB Primary database β€” 2 tables, on-demand billing, GSIs for efficient querying
Amazon S3 3 private buckets: public frontend, admin frontend, and media storage
Amazon CloudFront 2 CDN distributions β€” path-based routing, TLS termination, OAC to S3
Amazon Cognito Admin authentication β€” User Pool as a native API Gateway authorizer
Amazon EventBridge Custom event bus β€” async decoupling of API writes from side effects
Amazon SES Transactional email β€” sends lead notification emails to admin
Amazon SQS 3 Dead Letter Queues β€” captures and alerts on failed async operations
AWS CodePipeline 3 separate CI/CD pipelines (backend, admin frontend, public frontend)
AWS CodeBuild Builds Lambda packages and Vite SPAs, reads config from SSM
AWS CodeDeploy Canary Lambda deployments β€” 10% traffic for 5 min, auto-rollback
AWS X-Ray Distributed tracing across Lambda, API Gateway, and DynamoDB
Amazon CloudWatch Structured logs, custom alarms, and a unified observability dashboard
AWS IAM 16 custom per-function least-privilege policies
AWS KMS Encryption of CodePipeline build artifacts
AWS SSM Parameter Store Build-time and runtime configuration injection
Amazon SNS Alarm notification topic β€” emails on CloudWatch alarm triggers
Amazon Route 53 DNS routing to CloudFront distributions

⚑ Lambda Functions

Six single-purpose Lambda functions β€” each with its own IAM role, log group, and alarm set. No god functions.

Function Trigger Responsibility Services Accessed
admin_blog_posts API Gateway (Admin) Full CRUD + lifecycle (publish, archive, delete) on posts DynamoDB (R/W), EventBridge
public_posts_lambda API Gateway (Public) Read-only access to published posts DynamoDB (Query via GSI)
leads_lambda API Gateway (Both) Create leads (public) Β· Read leads (admin) DynamoDB (R/W), EventBridge
presign_lambda API Gateway (Admin) Generate time-limited S3 upload URLs S3 (PutObject only)
notifications_lambda EventBridge Send lead notification email SES
cleanup_lambda EventBridge Delete S3 media when a post is deleted S3 (DeleteObject only)

Shared Lambda Layer: All functions use a common layer containing the AWS SDK and X-Ray SDK, reducing deployment package sizes and keeping dependencies consistent.

Runtime:     Node.js 18.x
Tracing:     AWS X-Ray (active mode)
Log retention: 7 days per function
Aliases:     live (production traffic) Β· beta (canary target)

Blast radius design: If the Notifications Lambda fails, it has zero impact on the blog API or admin CMS. Each function fails independently.


🌐 API Design β€” API Gateway

Two completely separate REST APIs serve two completely different audiences.

  PUBLIC API GATEWAY                    ADMIN API GATEWAY
  ─────────────────                     ─────────────────
  No authentication                     Cognito User Pool Authorizer
  GET  /posts                           GET    /admin/posts
  GET  /posts/{postId}                  POST   /admin/posts
  POST /leads                           PUT    /admin/posts/{id}
                                        DELETE /admin/posts/{id}
                                        POST   /admin/posts/{id}/publish
                                        POST   /admin/posts/{id}/unpublish
                                        POST   /admin/posts/{id}/archive
                                        POST   /admin/media/upload_url
                                        GET    /admin/leads

CloudFront as the single entry point: All traffic enters through CloudFront distributions. A CloudFront Function rewrites path prefixes (/api/* β†’ /*) before forwarding to API Gateway, so both frontends use the same domain for API and static assets β€” eliminating CORS entirely.


πŸ—„οΈ Database Design β€” DynamoDB

Posts Table

Attribute Type Role
postId String (PK) Primary key
status String DRAFT / PUBLISHED / ARCHIVED
publishedAt String ISO timestamp used for sort
authorId String Author reference
title, content, tags Various Post content fields

Global Secondary Indexes:

GSI Name Partition Key Sort Key Query Use Case
publishedAtIndex status publishedAt Public blog β€” list all published posts in date order
authorIDIndex authorId createdAt Admin view β€” list posts by author

CQRS (Lite) Approach: The public Lambda only has Query permission on the GSI. The admin Lambda has full CRUD. The same table serves both read and write paths β€” but IAM enforces the segregation so the public function physically cannot write to the table.

Leads Table

Stores lead submissions from the public blog. Admin Lambda can read all leads. Only leads Lambda can write.


πŸ” Authentication β€” Amazon Cognito

Amazon Cognito User Pool provides JWT-based authentication for the admin CMS.

sequenceDiagram
    actor Admin as πŸ‘€ Admin Browser
    participant Amplify as πŸ“± AWS Amplify
    participant Cognito as πŸ” Amazon Cognito
    participant APIGW as 🌐 API Gateway
    participant Lambda as ⚑ Lambda

    Admin->>Amplify: Enter credentials
    Amplify->>Cognito: Authenticate
    Cognito-->>Amplify: βœ… JWT ID Token
    Amplify-->>Admin: Signed in

    Admin->>APIGW: API request + Authorization Bearer JWT
    APIGW->>Cognito: Validate token
    alt βœ… Valid Token
        Cognito-->>APIGW: Authorized
        APIGW->>Lambda: Invoke
        Lambda-->>Admin: 200 OK
    else ❌ Invalid or Missing Token
        Cognito-->>APIGW: Unauthorized
        APIGW-->>Admin: 401 β€” Lambda never invoked
    end
Loading

Key design decisions:

  • Admin-create-only β€” no self-registration. Users must be provisioned by an admin.
  • Authorization at the infrastructure layer β€” Cognito is enforced at API Gateway, not in Lambda code. Lambda never receives an unauthenticated request.
  • Public API has zero Cognito dependency β€” public users are never impacted by auth service issues.

πŸ“‘ Event-Driven Architecture β€” EventBridge

A custom EventBridge bus decouples API handlers from their side effects.

flowchart TD
    classDef userStyle fill:#6e2f1a,stroke:#e59866,color:#fff
    classDef lambdaStyle fill:#145a32,stroke:#27ae60,color:#fff,rx:6
    classDef dbStyle fill:#1b2631,stroke:#717d7e,color:#fff,rx:6
    classDef busStyle fill:#784212,stroke:#f39c12,color:#fff
    classDef asyncStyle fill:#4a235a,stroke:#9b59b6,color:#fff,rx:6
    classDef dlqStyle fill:#7b241c,stroke:#e74c3c,color:#fff,rx:6

    U(["πŸ‘€ User"]):::userStyle -->|POST /leads| LL["⚑ Leads Lambda"]:::lambdaStyle
    A(["πŸ” Admin"]):::userStyle -->|DELETE /admin/posts/:id| AL["⚑ Admin Lambda"]:::lambdaStyle

    LL --> DDB1[("πŸ—„οΈ DynamoDB<br/>Write Lead")]:::dbStyle
    LL -->|"βœ… instant 201"| U
    LL -->|"πŸ“€ emit LeadCreated"| EB(["🚌 EventBridge Bus"]):::busStyle

    AL --> DDB2[("πŸ—„οΈ DynamoDB<br/>Delete Post")]:::dbStyle
    AL -->|"βœ… instant 200"| A
    AL -->|"πŸ“€ emit PostDeleted"| EB

    EB -->|LeadCreated rule| NL["πŸ”” Notifications Lambda"]:::asyncStyle
    NL --> SES["πŸ“§ SES Β· Email to Admin"]:::asyncStyle
    NL -->|fail after 10 retries| DLQ1["⚠️ SQS DLQ"]:::dlqStyle

    EB -->|PostDeleted rule| CL["🧹 Cleanup Lambda"]:::asyncStyle
    CL --> S3M["πŸ—‘οΈ S3 Β· Delete Media"]:::dbStyle
    CL -->|fail after 10 retries| DLQ2["⚠️ SQS DLQ"]:::dlqStyle

    DLQ1 --> CWA["🚨 CloudWatch Alarm"]:::dlqStyle
    DLQ2 --> CWA
Loading

Why this matters for the user: The user gets an instant API response. The email notification or media cleanup happens behind the scenes without adding to their response time.

Resilience: Each EventBridge rule targets the Lambda with a retry policy (up to 10 retries). Failed events after exhausting retries are sent to an SQS Dead Letter Queue, which triggers a CloudWatch alarm.


πŸ“Ž Secure Media Handling β€” Presigned URLs

File uploads bypass Lambda entirely using the Valet Key pattern.

sequenceDiagram
    actor Browser as πŸ–₯️ Browser
    participant APIGW as 🌐 API Gateway Admin
    participant Cognito as πŸ” Cognito Authorizer
    participant Lambda as ⚑ Presign Lambda
    participant S3 as πŸ—‚οΈ S3 Media Bucket
    participant CF as ☁️ CloudFront CDN

    Note over Browser,CF: πŸ“‹ Phase 1 β€” Request Upload Permission
    Browser->>APIGW: POST /admin/media/upload_url
    APIGW->>Cognito: Validate JWT
    Cognito-->>APIGW: βœ… Authorized
    APIGW->>Lambda: Invoke
    Lambda->>Lambda: πŸ” Validate content-type (image/jpeg Β· image/png)
    Lambda->>S3: Generate presigned PUT URL Β· 5 min TTL
    S3-->>Lambda: Presigned URL
    Lambda-->>Browser: presigned_url in response body

    Note over Browser,CF: πŸ“€ Phase 2 β€” Direct Upload (No Lambda Β· No API Gateway)
    Browser->>S3: PUT presigned URL with file bytes
    S3-->>Browser: βœ… 200 OK

    Note over Browser,CF: πŸ–ΌοΈ Phase 3 β€” Serve via CDN
    Browser->>CF: GET /media/image.jpg
    CF->>S3: Fetch via OAC (private bucket)
    S3-->>CF: Image bytes
    CF-->>Browser: ⚑ Cached image response
Loading

Security note: The S3 media bucket has no public access. The presign Lambda validates allowed content-types before generating the URL β€” preventing malicious file types from being hosted.


πŸš€ CI/CD Pipeline

Three separate CodePipeline pipelines ensure that a change to the public frontend never triggers a backend deployment.

flowchart TD
    classDef sourceStyle fill:#1a4f7a,stroke:#2e86c1,color:#fff,rx:6
    classDef buildStyle fill:#145a32,stroke:#27ae60,color:#fff,rx:6
    classDef deployStyle fill:#4a235a,stroke:#9b59b6,color:#fff,rx:6
    classDef successStyle fill:#1d6a27,stroke:#2ecc71,color:#fff,rx:6
    classDef rollbackStyle fill:#7b241c,stroke:#e74c3c,color:#fff,rx:6

    GH["πŸ“¦ GitHub<br/>Source Code"]:::sourceStyle --> BE & AF & PF

    BE["πŸ”§ Backend Pipeline"]:::sourceStyle --> CB_BE["πŸ—οΈ CodeBuild<br/>Package Lambda ZIPs"]:::buildStyle
    CB_BE --> CD["πŸš€ CodeDeploy<br/>Canary 10% Β· 5 min"]:::deployStyle
    CD -->|"βœ… healthy"| LIVE["✨ Lambda live alias<br/>100% v2"]:::successStyle
    CD -->|"🚨 alarm fires"| RB["↩️ Auto Rollback<br/>to v1"]:::rollbackStyle

    AF["πŸ–₯️ Admin FE Pipeline"]:::sourceStyle --> CB_AF["πŸ—οΈ CodeBuild<br/>npm build + SSM env inject"]:::buildStyle
    CB_AF --> S3_AF["☁️ S3 Sync +<br/>CloudFront Invalidate"]:::deployStyle

    PF["🌍 Public FE Pipeline"]:::sourceStyle --> CB_PF["πŸ—οΈ CodeBuild<br/>npm build + SSM env inject"]:::buildStyle
    CB_PF --> S3_PF["☁️ S3 Sync +<br/>CloudFront Invalidate"]:::deployStyle
Loading

Canary Deployment (Lambda)

CodeDeploy shifts 10% of Lambda traffic to the new version for 5 minutes before completing the rollout. If a CloudWatch alarm fires during that window, traffic is automatically rolled back to the previous version β€” no manual intervention needed.

flowchart TD
    classDef deployStyle fill:#1a4f7a,stroke:#2e86c1,color:#fff,rx:6
    classDef watchStyle fill:#784212,stroke:#f39c12,color:#fff,rx:6
    classDef successStyle fill:#145a32,stroke:#27ae60,color:#fff,rx:6
    classDef rollbackStyle fill:#7b241c,stroke:#e74c3c,color:#fff,rx:6
    classDef decisionStyle fill:#17202a,stroke:#717d7e,color:#fff

    D["πŸš€ New Lambda Version v2<br/>deployed"]:::deployStyle --> S["πŸ”€ Traffic Shift<br/>90% v1 Β· 10% v2"]:::deployStyle
    S --> W["⏱️ 5 Minute<br/>Watch Window"]:::watchStyle
    W --> M["πŸ“Š CloudWatch<br/>Monitors error alarms"]:::watchStyle
    M --> OK{"πŸ” Alarm<br/>fired?"}:::decisionStyle
    OK -->|"βœ… No alarms"| FULL["πŸŽ‰ 100% traffic to v2<br/>Deployment complete"]:::successStyle
    OK -->|"🚨 Alarm fired"| RB["↩️ Automatic rollback<br/>100% back to v1"]:::rollbackStyle
Loading

Build-time Config Injection

CodeBuild reads environment config from SSM Parameter Store and injects Terraform-generated values (API URLs, Cognito IDs, CloudFront domains) as Vite environment variables at build time β€” no hardcoded values, no manual copy-pasting between services.


πŸ”­ Observability & Monitoring

The three pillars of observability are all implemented.

1 β€” Distributed Tracing: AWS X-Ray

Every Lambda function and API Gateway stage is instrumented with X-Ray. A single user request produces a trace spanning CloudFront β†’ API Gateway β†’ Lambda β†’ DynamoDB, with subsegments for each service call.

alt text

2 β€” Structured Logging: CloudWatch Logs

All Lambda functions emit structured JSON logs with a correlationId, so a single request can be traced across multiple log groups.

3 β€” Metrics & Alarms: CloudWatch

Alarm Metric Threshold Action
Lambda error rate Errors per function > 1 per 5 min SNS β†’ email
API Gateway 5xx errors 5XXError > 1 per 5 min SNS β†’ email
DLQ message count ApproximateNumberOfMessages > 0 SNS β†’ email
DynamoDB throttles ThrottledRequests > 1 SNS β†’ email
CloudFront 5xx rate 5xxErrorRate > 5% SNS β†’ email

A unified CloudWatch Dashboard provides a single-pane-of-glass view across all Lambda functions, API Gateways, DynamoDB tables, and queues.


πŸ›‘οΈ Security Design

Security is layered across every tier β€” not bolted on after the fact.

IAM Least Privilege β€” Permission Matrix

Each Lambda function has exactly the permissions it needs. Nothing more.

DynamoDB Posts DynamoDB Leads S3 Write S3 Delete EventBridge SES
admin_blog_posts βœ… CRUD βœ… Publish
public_posts_lambda βœ… Query only
leads_lambda βœ… CRUD βœ… Publish
presign_lambda βœ… PutObject
notifications_lambda βœ… SendEmail
cleanup_lambda βœ… DeleteObject

16 custom IAM policies β€” each scoped to a specific resource ARN. No wildcards. No shared roles.

Defence in Depth

flowchart TD
    classDef layer1Style fill:#1a4f7a,stroke:#2e86c1,color:#fff,rx:6
    classDef layer2Style fill:#145a32,stroke:#27ae60,color:#fff,rx:6
    classDef layer3Style fill:#7b241c,stroke:#e74c3c,color:#fff,rx:6
    classDef layer4Style fill:#4a235a,stroke:#9b59b6,color:#fff,rx:6
    classDef layer5Style fill:#7d6608,stroke:#f1c40f,color:#000,rx:6
    classDef endpointStyle fill:#1b2631,stroke:#717d7e,color:#fff

    R(["🌐 Incoming Request"]):::endpointStyle
    R --> L1["🌍 Layer 1 · Network Edge<br/>CloudFront HTTPS only · TLS 1.2<br/>No direct S3 or Lambda URLs"]:::layer1Style
    L1 --> L2["πŸ” Layer 2 Β· Authentication<br/>Cognito JWT at API Gateway<br/>for all admin routes"]:::layer2Style
    L2 --> L3["πŸ›‘οΈ Layer 3 Β· Authorization<br/>16 per-Lambda IAM policies<br/>Public Lambda cannot write"]:::layer3Style
    L3 --> L4["πŸ”’ Layer 4 Β· Data<br/>S3 Block Public Access Β· CloudFront OAC<br/>DynamoDB encryption at rest"]:::layer4Style
    L4 --> L5["βœ… Layer 5 Β· Application<br/>Content-type validation Β· Input validation<br/>No self-registration"]:::layer5Style
    L5 --> RES(["✨ Request Processed<br/>Securely"]):::endpointStyle
Loading

πŸ—οΈ Infrastructure as Code β€” Terraform

The entire platform is defined in Terraform. Zero manual console configuration.

Terraform File AWS Resources Created
lambda.tf 6 Lambda functions, 1 shared layer, aliases, log groups
api_gateway.tf 2 API Gateways, stages, methods, Cognito authorizer
database.tf 2 DynamoDB tables with GSIs, encryption, contributor insights
s3_buckets.tf 3 S3 buckets with lifecycle rules, versioning, CORS, policies
cloudfront.tf 2 CloudFront distributions, OAC, custom cache behaviours
auth.tf Cognito User Pool and App Client
eventbridge.tf Custom event bus, rules, targets, retry policies, DLQ targets
lambda.tf SQS Dead Letter Queues
ci_cd_*.tf 3 CodePipeline pipelines, CodeBuild projects, CodeDeploy groups
cloudwatch_dashboard.tf Dashboard, alarms, SNS topic
iam/ 16 custom IAM policies with resource-level scoping
ssm.tf 8 SSM parameters for build-time config injection
ses.tf SES verified email identity
route53.tf DNS records pointing to CloudFront
Terraform version:  ~> 1.14
AWS Provider:       ~> 5.0
Region:             eu-west-1 (Ireland)
Resource prefix:    sblg-  (e.g. sblg-posts, sblg-media-bucket)

πŸ’» Frontend Applications

Admin CMS (React + TypeScript + Vite)

A full-featured content management system for managing posts, media, and leads.

Library Purpose
React 19 + TypeScript UI framework with type safety
TanStack Query v5 Server state management and caching
React Hook Form + Zod Form handling with schema validation
AWS Amplify v6 Cognito authentication integration
TipTap v3 Rich text editor for post content
Tailwind CSS v4 + Radix UI Accessible, utility-first UI
Zustand Lightweight client state management

Public Blog (React + TypeScript + Vite)

An intentionally lightweight, read-only blog viewer.

Library Purpose
React 19 + TypeScript UI framework
TanStack Query v5 API data fetching with caching
Axios HTTP client

Design decision: The public frontend has no auth library, no rich text editor, no form framework β€” it only reads and displays content. Keeping it lean reduces bundle size and attack surface.


πŸ“ Project Structure

AWS_SERVERLESS_BLOG/
β”‚
β”œβ”€β”€ backend/                        # Lambda function source code (Node.js)
β”‚   β”œβ”€β”€ admin_blog_post/            # Posts CRUD + lifecycle operations
β”‚   β”œβ”€β”€ public_posts_lambda/        # Read-only published post access
β”‚   β”œβ”€β”€ leads_lambda/               # Lead creation + admin reads
β”‚   β”œβ”€β”€ presign_lambda/             # S3 presigned URL generation
β”‚   β”œβ”€β”€ notifications_lambda/       # SES email on new lead
β”‚   β”œβ”€β”€ cleanup_lambda/             # S3 media cleanup on post deletion
β”‚   └── blog_lambda_layer/          # Shared Layer (AWS SDK + X-Ray SDK)
β”‚
β”œβ”€β”€ infrastructure/                 # All AWS infrastructure as Terraform
β”‚   β”œβ”€β”€ main.tf                     # Provider config
β”‚   β”œβ”€β”€ lambda.tf                   # Lambda functions + aliases + layers
β”‚   β”œβ”€β”€ api_gateway.tf              # REST APIs + Cognito authorizer
β”‚   β”œβ”€β”€ database.tf                 # DynamoDB tables + GSIs
β”‚   β”œβ”€β”€ s3_buckets.tf               # S3 buckets + bucket policies
β”‚   β”œβ”€β”€ cloudfront.tf               # CDN distributions
β”‚   β”œβ”€β”€ auth.tf                     # Cognito User Pool
β”‚   β”œβ”€β”€ eventbridge.tf              # Event bus + rules + DLQs
β”‚   β”œβ”€β”€ ci_cd_*.tf                  # 3 CI/CD pipelines
β”‚   β”œβ”€β”€ cloudwatch_dashboard.tf     # Dashboard + alarms
β”‚   β”œβ”€β”€ ssm.tf                      # Parameter Store entries
β”‚   └── iam/policies/               # 16 granular IAM policy documents
β”‚
β”œβ”€β”€ admin-frontend/                 # React + Vite admin CMS (TypeScript)
β”‚   └── src/
β”‚       β”œβ”€β”€ api/                    # Axios API client functions
β”‚       β”œβ”€β”€ components/             # Reusable UI components
β”‚       β”œβ”€β”€ pages/                  # Route-level page components
β”‚       β”œβ”€β”€ hooks/                  # Custom React hooks
β”‚       └── store/                  # Zustand auth store
β”‚
β”œβ”€β”€ public-frontend/                # React + Vite public blog (TypeScript)
β”‚   └── src/
β”‚       β”œβ”€β”€ api/                    # Public API client functions
β”‚       β”œβ”€β”€ components/             # Blog UI components
β”‚       └── pages/                  # Route-level page components
β”‚
β”œβ”€β”€ buildspec.backend.yml           # CodeBuild spec β€” Lambda packaging + deploy
β”œβ”€β”€ buildspec.admin-frontend.yml    # CodeBuild spec β€” Admin SPA build + S3 sync
└── buildspec.public-frontend.yml   # CodeBuild spec β€” Public SPA build + S3 sync

πŸŽ“ Key Skills Demonstrated

This project was built to showcase practical, production-relevant AWS skills. Here's what each section of the system demonstrates:

Skill Area Demonstrated By
Serverless architecture 6 Lambda functions, API Gateway, DynamoDB β€” zero servers to manage
AWS security best practices 16 least-privilege IAM policies, Cognito auth, private S3 + CloudFront OAC
Event-driven design EventBridge custom bus, pub/sub pattern, decoupled async workflows
Infrastructure as Code 100% Terraform β€” no manual console changes, all resources version-controlled
CI/CD on AWS 3 CodePipeline pipelines with CodeBuild, CodeDeploy canary deployments
Observability X-Ray distributed tracing, structured CloudWatch logging, custom alarms
DynamoDB data modelling GSI design for efficient read patterns, CQRS-lite via IAM separation
Resilience patterns SQS Dead Letter Queues, retry policies, canary rollback, bulkhead isolation
CDN & static hosting CloudFront with path-based routing, S3 SPA hosting, TLS termination
Secure file handling Presigned URLs (Valet Key pattern), content-type validation
Configuration management SSM Parameter Store for build-time config injection via CodeBuild
Cost optimisation On-demand DynamoDB, S3 lifecycle rules, CloudFront caching, short log retention

πŸ‘€ Author

Shaun AWS Cloud Engineer


Every resource, every IAM policy, every alarm β€” all defined in the infrastructure/ directory. No ClickOps.

About

A production-grade serverless blog and content platform built on AWS, designed to demonstrate real-world cloud architecture patterns: secure content management, scalable public reads, media delivery via CDN, event-driven notifications, and infrastructure fully defined using Terraform.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors