DEV Community

Cover image for Building a Serverless Life Visualization App with Next.js, AWS Lambda, and DynamoDB Single-Table Design - #01
Randika Madhushan Perera
Randika Madhushan Perera

Posted on

Building a Serverless Life Visualization App with Next.js, AWS Lambda, and DynamoDB Single-Table Design - #01

How we built yourdeath.date - a memento mori tool that renders your life as a grid of weeks - on a fully serverless AWS stack

Introduction


Most apps are built to keep you engaged as long as possible. We built one designed to remind you that your time is finite.

yourdeath.date visualizes your entire life from birth to projected death as a grid of small squares. Each square is one week. Filled squares are weeks already spent. The rest are blank.

The app also lets users log "good deeds" onto specific weeks, pin life milestones to the grid, share their grid publicly, and connect with a community of people thinking about how they spend their time.

This article covers the technical architecture behind it. The AWS services, the serverless design decisions, the DynamoDB single-table schema, the auth flow, and the frontend rendering approach.

What is this Application?


Frontend -> Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS
Backend -> AWS Lambda (Node.js 22.x), API Gateway REST
Database -> DynamoDB (single-table design, 3 GSIs)
Auth -> Amazon Cognito (User Pool + Identity Pool)
Email -> Amazon SES + Secrets Manager
Storage Amazon -> S3
Scheduling -> Amazon EventBridge
IaC -> AWS SAM (Serverless Application Model)
Hosting -> AWS Amplify (frontend), API Gateway (backend)

High Level Architecture


High Level Architecture - AWS

Application FLow


User Onboarding Flow

  1. User visits yourdeath.date
  2. Clicks "See your life →"
  3. Redirected to /auth (Cognito hosted UI or email/password)
  4. OAuth callback → /auth/callback
  5. Amplify exchanges code for tokens (access + id + refresh)
  6. POST /auth/sync → Lambda syncs Cognito identity to DynamoDB
  7. Redirect to /onboarding → user enters DOB + life expectancy
  8. PUT /user/profile → stores profile in DynamoDB
  9. Redirect to /dashboard → LifeGrid renders with real data

Deed Logging Flow

  1. User opens community page
  2. Frontend reads current week index: Math.floor(msAlive / msPerWeek)
  3. User fills deed form (description, category, helpedWho, isPublic)
  4. POST /deeds → Lambda validates, stores in DynamoDB
    • PK: USER#{userId} SK: DEED#{weekIndex:6-padded}#{deedId}
    • GSI1PK: COMMUNITY#PUBLIC (if public) or USER#{userId}#PRIVATE
  5. Dashboard: GET /deeds/me returns {weekIndex → count} map
  6. LifeGrid highlights that week column green

Weekly Email Flow

EventBridge cron(0 6 ? * MON *) → WeeklyEmailFunction
→ ScanCommand: FilterExpression = emailOptIn = true
→ For each user:
→ Fetch weeks lived, weeks remaining
→ Generate HMAC token: sha256(userId:email, secret)
→ Build HTML email with unsubscribe link
→ SES SendEmail (10-message batches, 100ms throttle)

AWS Services Connectivity Flow

AWS Services Connectivity Flow

Interesting Engineering Decisions


  1. Single-Table DynamoDB vs Multi-Table

We choose single table design to minimize cold-start latency (one connection pool) and reduce operational overhead. The trade-off is query complexity, GSI3 exists purely to solve the deed-by-ID lookup problem that arises from putting deeds under USER#{id} as primary key.

  1. HMAC Unsubscribe Tokens

Rather than storing one-time tokens in DynamoDB, the unsubscribe system uses a stateless HMAC approach:

token = base64url( HMAC-SHA256( "{userId}:{email}", secret ) )

Validation is a single HMAC re-computation + constant-time comparison. No database lookup required. The secret lives in Secrets Manager and is fetched once per Lambda cold start.

Conclusion


The stack — Next.js on Amplify, API Gateway, Lambda, DynamoDB, Cognito — is a proven serverless pattern that scales to thousands of concurrent users with near-zero operational overhead. The interesting parts were the DynamoDB single-table schema design (especially GSI3 for deed lookup), the theme-aware LifeGrid renderer, and keeping analytics collection genuinely privacy-respecting.

The full codebase lives at yourdeath.date. If you have questions about any of the architecture decisions, drop them in the comments.


Built with AWS SAM · Next.js 14 · DynamoDB · Cognito · TypeScript

Top comments (0)