A production-ready Next.js 15 application for landscape restoration assessments featuring multi-language support (EN/ES/FR/PT), interactive maps, rich text editing, and XLSX export โ backed by TypeORM + PostgreSQL and deployed to AWS ECS Fargate via Terraform and GitHub Actions.
graph TB
subgraph AWS Cloud
subgraph VPC
subgraph Public Subnets
ALB[Application Load Balancer]
ECS[ECS Fargate Tasks<br/>Ephemeral Public IPs]
end
end
ECR[ECR Repository]
CW[CloudWatch Logs]
RDS[(RDS PostgreSQL)]
S3[S3 - TF State]
end
Client([Browser]) -->|HTTPS| ALB
ALB --> ECS
ECS --> RDS
GH([GitHub Actions]) -->|Deploy| ECR
ECR --> ECS
ECS -->|Logs| CW
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router), React 19 |
| UI | WRI Design System, Chakra UI v3, Tailwind CSS, TipTap Rich Text Editor |
| Forms | React Hook Form |
| Database | PostgreSQL 17, TypeORM 0.3 |
| Auth | Bcrypt password hashing, session tokens (Web Crypto API) |
| i18n | 4 languages (EN, ES, FR, PT) with JSON translation files |
| Maps | Interactive map components with layer/legend controls |
| Export | XLSX assessment export |
| Analytics | Google Tag Manager, Hotjar |
| Deployment | AWS ECS Fargate, ALB, ECR, Terraform |
| CI/CD | GitHub Actions (QA, Production, PR checks) |
| Testing | Jest, React Testing Library |
.
โโโ .github/
โ โโโ workflows/
โ โโโ deploy-qa.yml # QA deployment workflow
โ โโโ deploy-production.yml # Production deployment workflow
โ โโโ pr-check.yml # Pull request validation
โ โโโ destroy.yml # Infrastructure teardown
โโโ src/
โ โโโ app/ # Next.js App Router
โ โ โโโ api/
โ โ โ โโโ health/ # Health check endpoint
โ โ โ โโโ assessments/ # Assessment REST API
โ โ โ โโโ route.ts # POST create assessment
โ โ โ โโโ [id]/
โ โ โ โโโ answers/ # GET/POST answers
โ โ โ โโโ contributors/ # GET/POST/DELETE contributors
โ โ โ โโโ export-responses/ # XLSX export
โ โ โ โโโ login/ # Password authentication
โ โ โ โโโ preparation/ # Preparation workflow
โ โ โ โโโ questions/ # GET questions by language
โ โ โโโ assessment/
โ โ โ โโโ setup/ # Assessment setup form
โ โ โ โโโ [id]/
โ โ โ โโโ page.tsx # Overview & password prompt
โ โ โ โโโ created/ # Success page
โ โ โ โโโ preparation/ # Multi-step preparation
โ โ โ โ โโโ [step]/
โ โ โ โโโ [theme]/ # Theme pages (Motivate/Enable/Implement)
โ โ โโโ auth/ # Sign-in & sign-up pages
โ โ โโโ welcome/ # Welcome page
โ โโโ components/
โ โ โโโ assessment/ # Assessment UI components
โ โ โ โโโ DiagnosticPreparation/ # Multi-step preparation form
โ โ โ โโโ Overview/ # Assessment overview & export
โ โ โ โโโ AnswerOptions.tsx # Yes/Partly/No/NA selection
โ โ โ โโโ ChakraRichTextEditor.tsx # Rich text editor
โ โ โ โโโ ContributorsCombobox.tsx # Team member selection
โ โ โ โโโ FollowUpQuestions.tsx # Dynamic follow-ups
โ โ โ โโโ PasswordPrompt.tsx # Access control
โ โ โโโ Map/ # Interactive map with layers & legends
โ โ โโโ static/landing/ # Landing page sections
โ โ โโโ icons/ # 50+ custom SVG icons
โ โ โโโ Footer/
โ โ โโโ Providers/ # Chakra UI & language context
โ โ โโโ ui/ # Shared UI (DatePicker, RichText, Loader)
โ โโโ db/ # Database layer
โ โ โโโ schema.dbml # Database schema (source of truth)
โ โ โโโ data-source.ts # TypeORM configuration
โ โ โโโ entities/ # 8 TypeORM entities
โ โ โ โโโ Assessment.entity.ts # Assessment instance
โ โ โ โโโ Answer.entity.ts # Answer (yes/partly/no/na)
โ โ โ โโโ AnswerContributor.entity.ts # Junction table
โ โ โ โโโ Contributor.entity.ts # Team members
โ โ โ โโโ Diagnostic.entity.ts # Assessment template
โ โ โ โโโ Lead.entity.ts # Assessment lead (demographics)
โ โ โ โโโ Question.entity.ts # Questions (3 themes)
โ โ โ โโโ Region.entity.ts # Geography & GIS
โ โ โโโ migrations/ # 15+ database migrations
โ โ โโโ seeds/ # Diagnostic & question seeds
โ โ โโโ queries/ # Complex assessment queries
โ โ โโโ scripts/ # DB utility scripts
โ โโโ i18n/ # Internationalization
โ โ โโโ translations/ # EN, ES, FR, PT JSON files
โ โ โโโ scripts/ # Import/export/validate scripts
โ โโโ hooks/ # Custom React hooks
โ โ โโโ useAutoSave.ts # Auto-save answers
โ โ โโโ useAssessmentSetupForm.ts # Setup form logic
โ โ โโโ useRichTextEditor.ts # Editor state
โ โ โโโ useResponsiveFlags.ts # Responsive helpers
โ โโโ contexts/ # React contexts
โ โ โโโ LanguageContext.tsx # Global language state
โ โโโ constants/ # App constants & options
โ โโโ types/ # TypeScript type definitions
โ โโโ utils/ # Utilities (session, rate-limiter, xlsx)
โ โโโ middleware.ts # Auth middleware for protected routes
โโโ terraform/
โ โโโ backend-setup/ # Terraform state backend (S3 + DynamoDB)
โ โโโ infrastructure/ # VPC, ALB, ECS, ECR, Security Groups
โ โโโ environments/ # QA & production tfvars
โโโ docs/resources/ # CSV question sources & schema reference
โโโ Dockerfile # Multi-stage build (standalone Next.js)
โโโ docker-compose.yml # Local PostgreSQL for development
โโโ package.json
erDiagram
Diagnostic ||--o{ Question : contains
Diagnostic ||--o{ Assessment : "template for"
Assessment }o--|| Lead : "led by"
Assessment }o--|| Region : "scoped to"
Assessment ||--o{ Answer : has
Assessment ||--o{ Contributor : has
Answer }o--|| Question : "responds to"
Answer ||--o{ AnswerContributor : "attributed to"
Contributor ||--o{ AnswerContributor : participates
Diagnostic {
uuid id PK
string title
string version
string language
}
Question {
uuid id PK
string questionCode
string theme "Motivate-Enable-Implement"
string enablingCondition
string keySuccessFactor
string questionText
int sortOrder
string locale
}
Assessment {
uuid id PK
string passwordHash
string projectType "GEF_8-WRI-other"
string status "draft-inprogress-completed-archived"
int diagnosticYear
timestamp submittedAt
}
Lead {
uuid id PK
string name
string email
string jobTitle
string organization
string gender
string ageRange
}
Region {
uuid id PK
string regionName
string geographyType
string countries
string ecosystems
string gisUrl
}
Answer {
uuid id PK
string value "yes-partly-no-na"
text rationale
text notes
string status "not_started-in_progress-complete"
}
Contributor {
uuid id PK
string name
}
AnswerContributor {
uuid contributorId PK
uuid answerId PK
}
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check |
POST |
/api/assessments |
Create assessment (returns ID + password) |
GET |
/api/assessments/[id]/questions |
Get questions by language |
GET |
/api/assessments/[id]/answers |
List all answers |
POST |
/api/assessments/[id]/answers |
Create answers |
GET |
/api/assessments/[id]/contributors |
List contributors |
POST |
/api/assessments/[id]/contributors |
Add contributor |
DELETE |
/api/assessments/[id]/contributors |
Remove contributor |
POST |
/api/assessments/[id]/login |
Verify password & create session |
GET |
/api/assessments/[id]/preparation |
Get preparation status |
POST |
/api/assessments/[id]/preparation |
Save preparation data |
GET |
/api/assessments/[id]/export-responses |
Export assessment to XLSX |
Four languages are supported with both UI and question translations:
| Language | Code | UI File | Questions File |
|---|---|---|---|
| English | en |
en.json |
questions-en.json |
| Spanish | es |
es.json |
questions-es.json |
| French | fr |
fr.json |
questions-fr.json |
| Portuguese | pt |
pt.json |
questions-pt.json |
Translation files are in src/i18n/translations/. Management scripts:
npm run i18n:import-csv # Import questions from CSV
npm run i18n:export-questions # Export questions to JSON
npm run i18n:import-questions # Import questions from JSON
npm run i18n:validate # Validate translation completeness
npm run i18n:cleanup # Remove duplicate questions- Node.js 24.15.0 (see
.nvmrc) - Docker
- Terraform 1.0+ (for AWS deployment)
- AWS CLI configured with appropriate credentials
- GitHub account
git clone <repository-url>
cd gri-restoration-diagnostic
# Use correct Node.js version
nvm use
# Install dependencies
npm installThe application uses a shared AWS RDS PostgreSQL instance (rd-app-db2) across all environments. Each environment (dev, qa, production) has its own database within the same RDS instance. Access to the DB instance is controlled by an AWS security group (rd-app-db1-sg), and each environment has access to it. Folks trying to connect to the DB directly need to add their external IP address to the AWS security group.
The RDS instance is publicly accessible to support local development. You must add your external IP to the AWS security group.
- Add inbound rule:
- Type: PostgreSQL
- Port: 5432
- Source:
<your-ip-address>/32 - Description: "[Your Name]"
# Copy example environment file
cp .env.example .envUpdate .env with RDS credentials:
# Database Configuration (AWS RDS)
DB_HOST=rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com
DB_PORT=5432
DB_USER=rduser
DB_PASSWORD=<get-from-aws-parameter-store>
DB_NAME=dev # or 'qa', 'production'
# TypeORM Configuration
TYPEORM_SYNCHRONIZE=false
TYPEORM_LOGGING=true
# Application
NODE_ENV=development
# SSL Configuration (enable for production)
DATABASE_SSL_REJECT_UNAUTHORIZED=falseGetting Database Credentials:
- Master password is stored in AWS Systems Manager Parameter Store
- Navigate to Parameter Store and search for
rd-app-db2
Get your external IP:
curl ifconfig.me# Run migrations to create tables
npm run migration:run
# Seed initial diagnostic questions (24 questions)
npm run seed:runGenerate migrations when schema changes:
npm run migration:generate# Connect to dev database
psql -h rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com -U postgres -d dev
# List tables
\dt
# View diagnostic seed data
SELECT id, version, language FROM diagnostic;
# Exit
\qNote: If connection times out, verify your IP is in the rd-app-db1-sg security group.
Before deploying infrastructure, you need to create the S3 bucket and DynamoDB table for Terraform state:
cd terraform/backend-setup
# Make the script executable
chmod +x setup.sh
# Run setup (uses default values)
./setup.sh
# Or customize with environment variables
AWS_REGION=us-east-1 PROJECT_NAME=rd-app ./setup.sh-
Create a new GitHub repository
-
Push this code to the repository
-
Create the following branches:
mainorproduction- Production deploymentsqa- QA deployments
-
Add GitHub variables for AWS permissions (Settings โ Secrets and variables โ Actions -> Variables):
OIDC_ROLE- ARN from AWS console for role GitHubActionsOIDC
The AWS credentials need permissions for:
- ECR (create/push images)
- ECS (manage clusters, services, tasks)
- EC2 (VPC, subnets, security groups)
- ELB (Application Load Balancers)
- IAM (create roles and policies)
- CloudWatch (logs)
- S3 (Terraform state)
- DynamoDB (Terraform locks)
Push to the appropriate branch to trigger deployment:
# Deploy to QA
git checkout -b qa
git push origin qa
# Deploy to Production
git checkout main
git push origin main# Run development server
npm run dev
# Run tests
npm test
# Run tests with coverage (CI mode)
npm run test:ci
# Lint
npm run lint
# Build for production
npm run build
# Start production server
npm startMigrations:
# Generate new migration (after entity changes)
npm run migration:generate
# Run pending migrations
npm run migration:run
# Revert last migration
npm run migration:revertSeeding:
# Run seed data
npm run seed:runDirect Database Access:
# Connect to dev database
psql -h rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com -U postgres -d dev
# Connect to QA database
psql -h rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com -U postgres -d qa
# Connect to production database
psql -h rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com -U postgres -d production# These commands are no longer used (RDS replaced local Docker)
# npm run db:start
# npm run db:stop
# npm run db:reset# Build image
docker build -t rd-app .
# Run container
docker run -p 3000:3000 rd-app- RDS Endpoint:
rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com - Engine: PostgreSQL 17
- Databases:
dev- Development databaseqa- QA databaseproduction- Production database
- Access: Publicly accessible with security group IP allowlist
- Credentials: Stored in AWS Systems Manager Parameter Store
- VPC CIDR:
10.0.0.0/16 - Database:
qa - Resources: 256 CPU / 512 MB Memory
- Desired count: 1 task
- Auto-scaling: 1-2 tasks
- VPC CIDR:
10.1.0.0/16 - Database:
production - Resources: 512 CPU / 1024 MB Memory
- Desired count: 2 tasks
- Auto-scaling: 2-10 tasks
| Workflow | Trigger | Description |
|---|---|---|
deploy-qa.yml |
Push to qa branch |
Deploy to QA environment |
deploy-production.yml |
Push to main/production |
Deploy to Production |
pr-check.yml |
Pull requests | Run tests and validate |
destroy.yml |
Manual | Tear down infrastructure |
- Go to Actions โ Destroy Infrastructure
- Select the environment (qa or production)
- Type
DESTROYto confirm - Run workflow
cd terraform/backend-setup
chmod +x teardown.sh
./teardown.sh- CloudWatch Logs:
/ecs/rd-app-{environment} - Container Insights: Enabled on ECS cluster
- Health Check:
GET /api/health
- VPC with public subnets; exposure limited via security groups and ALB
- ECS Fargate tasks with ephemeral public IPs (no NAT Gateway)
- Security groups limiting traffic
- S3 bucket versioning and encryption for Terraform state
- ECR image scanning on push
- Non-root container user
- HTTPS headers configured in Next.js
- Bcrypt password hashing for assessment access
- Session token authentication (Web Crypto API, Edge Runtime)
- Rate limiting on API endpoints
- Auth middleware protecting assessment routes
- AWS RDS SSL connections (CA bundle in Docker image)
- Use
FARGATE_SPOTfor non-production workloads - Auto-scaling based on CPU/Memory utilization
- ECR lifecycle policies to clean old images
- No NAT Gateway โ ECS tasks use ephemeral public IPs
- Update
terraform/environments/{env}.tfvars:
app_environment_variables = {
"MY_VAR" = "my-value"
}- Redeploy
Edit terraform/environments/{env}.tfvars:
container_cpu = 512 # 0.5 vCPU
container_memory = 1024 # 1 GB
desired_count = 3-
Database connection fails
- Verify your IP is in the
rd-app-db1-sgsecurity group allowlist - Check
.envhas correct RDS credentials from Parameter Store - Test connection:
psql -h rd-app-db2.c9o0i0gg61en.us-east-1.rds.amazonaws.com -U postgres -d dev - Verify RDS instance is running in AWS Console
- Verify your IP is in the
-
Connection timeout to RDS
- Add your external IP to security group:
curl ifconfig.me - Check VPN/firewall settings aren't blocking port 5432
- Verify you're using the correct database name (dev/qa/production)
- Add your external IP to security group:
-
Migration generation fails
- Ensure entities are properly imported in
data-source.ts - Verify TypeORM can connect to RDS (check credentials)
- Check entity decorators and column types
- Review
tsconfig.typeorm.jsonfor CommonJS compatibility
- Ensure entities are properly imported in
-
Deployment fails at ECS service stability
- Check CloudWatch logs
- Verify health check endpoint returns 200
- Check security group rules
- Ensure ECS tasks can connect to RDS
-
Terraform state lock error
- Wait for other deployments to complete
- If stuck, manually release lock in DynamoDB
-
Docker build fails
- Ensure all dependencies are in package.json
- Check for missing files in .dockerignore
MIT