This repository contains the starter application for the MapPoints AWS Developer Course.
You will work in this repository throughout all 10 modules, progressively adding AWS services to the same application.
MapPoints is an interactive map SPA where users can:
- Browse geographic points of interest on a world map
- Create new points with a title, description, and coordinates
- Upload photos to individual points
- Import multiple points at once from a CSV file
- Authenticate via AWS Cognito and make protected API calls
- View AI-generated image labels on uploaded photos (module 10 bonus)
gis-app-aws/
βββ frontend/ β React SPA (Vite + TypeScript) β complete starter
β βββ src/
β β βββ App.tsx β Root component, auth state, feature flag wiring
β β βββ api/client.ts β Typed API client (reads env vars at runtime)
β β βββ components/ β UI components (map, forms, upload, import)
β β βββ config/featureFlags.json β Active flags for current module
β β βββ types.ts β Shared TypeScript types
β βββ .env.example β Template for your local environment variables
β βββ index.html
β βββ package.json
β
βββ backend_node/ β Node.js Lambda handler stubs β implement these
β βββ point_service/handlers/
β β βββ getPointsList.ts β GET /points
β β βββ getPointById.ts β GET /points/{pointId}
β β βββ createPoint.ts β POST /points
β βββ import_service/handlers/
β β βββ getUploadUrl.ts β GET /upload?pointId=&fileName=
β β βββ importPointsFile.ts β GET /import?fileName=
β β βββ processUploadedPhoto.ts β S3 trigger on uploads/
β β βββ importFileParser.ts β S3 trigger on uploaded/
β β βββ catalogBatchProcess.ts β SQS trigger
β β βββ enrichPhoto.ts β S3 trigger on uploads/ (module 10)
β βββ authorization_service/handlers/
β β βββ basicAuthorizer.ts β Lambda Authorizer (module 7)
β β βββ postConfirmation.ts β Cognito Post Confirmation trigger (module 8)
β βββ package.json
β βββ tsconfig.json
β
βββ module_02_infra_node/ β CDK starter for module 2 (S3 + CloudFront)
β βββ lib/module-02-hosting-stack.ts
β βββ bin/
β βββ cdk.json
β βββ package.json
β
βββ FeatureGuard.tsx β React component: renders fallback when flag is off
βββ FeatureFlagsPanel.tsx β Dev-only panel for toggling flags in the browser
βββ featureFlags.ts β TypeScript type for the FeatureFlags shape
| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ | https://nodejs.org |
| AWS CLI v2 | latest | https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html |
| AWS CDK | 2.x | npm install -g aws-cdk |
| AWS account | free tier | https://aws.amazon.com/free |
cd frontend
npm install
npm run devOpen http://localhost:5173 β the map is visible, all backend-dependent features are shown as disabled placeholders.
cp frontend/.env.example frontend/.env.localEdit .env.local and fill in values as you deploy each module:
# Module 3+ β Points service API Gateway base URL
VITE_POINTS_API_URL=
# Module 5+ β Import service API Gateway base URL
VITE_IMPORT_API_URL=
# Module 8-9+ β Cognito configuration
VITE_COGNITO_USER_POOL_ID=
VITE_COGNITO_CLIENT_ID=
VITE_COGNITO_DOMAIN=
VITE_COGNITO_REDIRECT_URI=http://localhost:5173Rules:
- Leave blank any variable whose backend is not yet deployed β the API client skips calls when the URL is empty.
- For production (deployed to CloudFront) change
VITE_COGNITO_REDIRECT_URIto your CloudFront URL.
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
REGION=$(aws configure get region)
npx cdk bootstrap aws://$ACCOUNT/$REGION# Build the frontend first
cd frontend
npm install && npm run build
# Deploy S3 bucket + CloudFront distribution
cd ../module_02_infra_node
npm install
npm run cdk:deployThe stack output contains:
| Output | Description |
|---|---|
CloudFrontUrl |
Your public HTTPS frontend URL |
BucketName |
S3 bucket name (for reference) |
To tear down:
npm run cdk:destroyFeature flags control which parts of the UI and API are active. They live in frontend/src/config/featureFlags.json.
Never enable a flag for a feature you have not implemented yet. The API client will make real HTTP calls and get errors if the backend is not deployed.
{
"module": 2,
"ui": {
"showMap": true,
"enableCreatePoint": false,
"enableUploadPhoto": false,
"enableCsvImport": false,
"enableAuthButtons": false,
"showAiLabels": false
},
"api": {
"enablePointsApi": false,
"pointsSource": "mock"
},
"security": {
"enableBasicAuthForImport": false,
"enableCognito": false,
"requireAuthForCreatePoint": false
},
"async": {
"enableSqsPipeline": false,
"enableSnsNotifications": false
},
"ai": {
"enableRekognitionLabels": false,
"enableModeration": false,
"enableTextDetection": false
}
}| Flag | Enable in module | Description |
|---|---|---|
ui.showMap |
2 | Renders the Leaflet map |
api.enablePointsApi |
3 | App fetches points from API instead of hardcoded mock |
api.pointsSource=mock |
3 | Lambda returns static mock data |
api.pointsSource=dynamodb |
4 | Lambda reads from DynamoDB |
ui.enableCreatePoint |
4 | "Add Point" form is enabled |
ui.enableUploadPhoto |
5 | "Upload Photo" button is enabled |
ui.enableCsvImport |
5 | "Import CSV" button is enabled |
async.enableSqsPipeline |
6 | CSV import routes through SQS queue |
async.enableSnsNotifications |
6 | Import completion sends SNS email notification |
security.enableBasicAuthForImport |
7 | GET /import requires Authorization: Basic header |
security.requireAuthForCreatePoint |
8 | POST /points requires Cognito id_token |
security.enableCognito |
9 | App uses Cognito for auth (enables token handling) |
ui.enableAuthButtons |
9 | Login / Logout buttons are shown |
ai.enableRekognitionLabels |
10 | App calls Rekognition labels endpoint |
ui.showAiLabels |
10 | AI labels are displayed on point detail |
Use FeatureGuard to gate UI elements declaratively:
import { FeatureGuard } from "../../FeatureGuard";
<FeatureGuard enabled={flags.ui.enableCreatePoint} fallback={<span>Available in module 4</span>}>
<CreatePointForm onSubmit={handleCreate} />
</FeatureGuard>When enabled is false, the fallback is rendered. Default fallback: "Available in next modules".
After deploying each module, save the URL and put it in your .env.local.
| Endpoint | Method | Module | Description |
|---|---|---|---|
/points |
GET | 3 | List all points |
/points/{pointId} |
GET | 3 | Get single point (includes aiLabels in module 10) |
/points |
POST | 4 | Create a new point (protected by Cognito in module 8) |
| Endpoint | Method | Module | Description |
|---|---|---|---|
/upload |
GET | 5 | Get S3 presigned PUT URL for photo (?pointId=&fileName=) |
/import |
GET | 5 | Get S3 presigned PUT URL for CSV (?fileName=) β protected by Basic Auth in module 7 |
GET /points
[
{
"id": "uuid",
"title": "Eiffel Tower",
"description": "Iconic iron lattice tower",
"latitude": 48.8584,
"longitude": 2.2945,
"photoUrl": "https://...",
"createdAt": "2025-01-15T10:00:00Z",
"tags": ["landmark"],
"aiLabels": [{ "name": "Tower", "confidence": 99.5 }]
}
]POST /points request body
{
"title": "string (required)",
"description": "string",
"latitude": 48.8584,
"longitude": 2.2945
}GET /upload response
{ "url": "https://s3.amazonaws.com/..." }After deploying the Cognito stack in module 8, get the outputs:
aws cloudformation describe-stacks --stack-name <your-stack-name> \
--query 'Stacks[0].Outputs'| Stack Output | .env.local variable |
|---|---|
UserPoolId |
VITE_COGNITO_USER_POOL_ID |
UserPoolClientId |
VITE_COGNITO_CLIENT_ID |
UserPoolDomain |
VITE_COGNITO_DOMAIN |
| CloudFront URL (module 2) | VITE_COGNITO_REDIRECT_URI |
The app uses the Authorization Code Grant flow:
- Login button β redirects to Cognito Hosted UI
- After login β Cognito redirects back with
?code=... - App exchanges the code for
id_token+access_tokenat/oauth2/token - Tokens stored in
localStorage id_tokensent asAuthorizationheader onPOST /points
Module 7 protects GET /import with a Lambda Authorizer using HTTP Basic Auth.
The lambda reads a {github_login}=TEST_PASSWORD environment variable. The frontend sends:
Authorization: Basic base64({github_login}:TEST_PASSWORD)
Set the token in the browser before testing the CSV import:
localStorage.setItem("authorization_token", btoa("<your_github_login>:TEST_PASSWORD"));The app reads this value from localStorage automatically when security.enableBasicAuthForImport=true.
All Lambda handlers are in backend_node/. Each file has a TODO comment describing what to implement. Wire each handler to API Gateway routes or S3/SQS event sources in your own CDK stacks (starting from module 3).
Do not modify module_02_infra_node/ β it is a complete working CDK stack for the hosting layer.
# Start frontend dev server
cd frontend && npm run dev
# Build frontend for deployment
cd frontend && npm run build
# Deploy module 2 (S3 + CloudFront)
cd module_02_infra_node && npm run cdk:deploy
# Destroy module 2 stack
cd module_02_infra_node && npm run cdk:destroy
# Check AWS credentials
aws sts get-caller-identity
# Preview CDK changes before deploying
npx cdk diff