Infrastructure as Code for Mostage Studio using AWS CDK.
The infrastructure code is located in the infrastructure/ directory and is organized using service-based architecture for scalability and maintainability.
- Node.js (>= 18.x) - Installation Guide
- AWS CLI - Installation Guide
- AWS CDK CLI - Install via npm:
npm install -g aws-cdk - AWS Account with appropriate permissions
-
Install dependencies:
cd infrastructure npm install -
Configure AWS credentials:
aws configure
Enter the following information:
- AWS Access Key ID
- AWS Secret Access Key
- Default region (e.g.,
eu-central-1) - Default output format:
json
-
Bootstrap CDK (First time only):
cdk bootstrap aws://ACCOUNT-ID/REGION
Replace
ACCOUNT-IDwith your AWS account ID andREGIONwith your region (e.g.,eu-central-1).
After deploying the infrastructure for the first time, you need to configure the frontend with the stack outputs.
-
Build the project:
npm run build
-
Preview changes:
npm run diff:dev
-
Deploy:
npm run deploy:dev
After successful deployment, get the stack outputs:
aws cloudformation describe-stacks \
--stack-name StudioStack-dev \
--query 'Stacks[0].Outputs' \
--output tableYou will see outputs like:
--------------------------------------------------------------------
| Outputs |
+-------------------+----------------------------------------------+
| UserPoolId | eu-central-1_xxxxxxxxx |
| UserPoolClientId | xxxxxxxxxxxxxxxxxxxxxx |
| UserPoolRegion | eu-central-1 |
+-------------------+----------------------------------------------+
For Local Development:
Create a .env.local file in the frontend/ directory:
NEXT_PUBLIC_COGNITO_USER_POOL_ID=eu-central-1_xxxxxxxxx
NEXT_PUBLIC_COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_AWS_REGION=eu-central-1
NEXT_PUBLIC_API_URL=https://xxxxxxxxxx.execute-api.eu-central-1.amazonaws.com/devCopy the values from the stack outputs:
UserPoolId→NEXT_PUBLIC_COGNITO_USER_POOL_IDUserPoolClientId→NEXT_PUBLIC_COGNITO_CLIENT_IDUserPoolRegion→NEXT_PUBLIC_AWS_REGIONApiUrl→NEXT_PUBLIC_API_URL
For Production:
Go to your GitHub repository → Settings → Secrets and variables → Actions and add:
NEXT_PUBLIC_COGNITO_USER_POOL_ID→ Value fromUserPoolIdoutputNEXT_PUBLIC_COGNITO_CLIENT_ID→ Value fromUserPoolClientIdoutputNEXT_PUBLIC_AWS_REGION→ Value fromUserPoolRegionoutput (e.g.,eu-central-1)NEXT_PUBLIC_API_URL→ Value fromApiUrloutput
-
Build the project:
npm run build
-
Preview changes:
npm run diff:dev
-
Deploy:
npm run deploy:dev
-
Get outputs (if needed):
aws cloudformation describe-stacks \ --stack-name StudioStack-dev \ --query 'Stacks[0].Outputs' \ --output table
-
Build the project:
npm run build
-
Preview changes:
npm run diff:prod
-
Deploy:
npm run deploy:prod
-
Get outputs:
aws cloudformation describe-stacks \ --stack-name StudioStack-prod \ --query 'Stacks[0].Outputs' \ --output table -
Update GitHub Secrets (if outputs changed):
Frontend is deployed via AWS Amplify Hosting, managed through CDK.
-
GitHub Personal Access Token (for connecting repository):
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Create a new token with
reposcope (for private repos) orpublic_repo(for public repos) - Save the token securely
-
Connect Repository (after first deployment):
- After deploying the stack, go to AWS Console → Amplify
- Find your app:
mostage-studio-frontend-{environment} - Go to App settings → General → Repository
- Click "Connect repository" and authorize GitHub
- Select your repository and branch
Amplify configuration is done in dev.ts and prod.ts:
amplifyConfig: {
repository: "https://github.com/mostage-app/studio",
branch: "main",
// Optional: GitHub token secret ARN from AWS Secrets Manager
// githubTokenSecretArn: process.env.GITHUB_TOKEN_SECRET_ARN,
// Optional: Custom build spec
// buildSpec: "...",
// Optional: Additional environment variables
environmentVariables: {
// Add any additional env vars here
},
// Optional: Custom domain
// customDomain: {
// domainName: "studio.mostage.app",
// certificateArn: "arn:aws:acm:...",
// },
}Amplify automatically receives these environment variables from the stack:
NEXT_PUBLIC_COGNITO_USER_POOL_ID- From Cognito User PoolNEXT_PUBLIC_COGNITO_CLIENT_ID- From Cognito User Pool ClientNEXT_PUBLIC_AWS_REGION- AWS RegionNEXT_PUBLIC_API_URL- API Gateway URL
Additional variables can be added in amplifyConfig.environmentVariables.
Build configuration is defined in amplify.yml (root directory) or can be customized in amplifyConfig.buildSpec.
Default build spec:
version: 1
frontend:
phases:
preBuild:
commands:
- cd frontend
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: frontend/.next
files:
- "**/*"
cache:
paths:
- frontend/node_modules/**/*
- frontend/.next/cache/**/*-
Deploy Infrastructure (includes Amplify app creation):
cd infrastructure npm run deploy:dev # or deploy:prod
-
Connect Repository (first time only):
- Go to AWS Console → Amplify
- Find your app and connect the repository
- Amplify will automatically build and deploy on every push to the configured branch
-
View Deployment:
- Stack output will show:
AmplifyAppUrl - Or check AWS Console → Amplify → Your App → Hosting
- Stack output will show:
After connecting the repository, Amplify will automatically:
- Build on every push to the configured branch
- Deploy to the Amplify URL
- Show build logs and status in AWS Console
To add a custom domain:
-
Get SSL Certificate (if not already):
# Request certificate via ACM aws acm request-certificate \ --domain-name studio.mostage.app \ --validation-method DNS \ --region us-east-1 # Must be us-east-1 for Amplify
-
Update Stack Config:
amplifyConfig: { // ... other config customDomain: { domainName: "studio.mostage.app", certificateArn: "arn:aws:acm:us-east-1:...", }, }
-
Redeploy:
npm run deploy:dev # or deploy:prod
Build fails:
- Check build logs in AWS Console → Amplify → Your App → Build history
- Verify environment variables are set correctly
- Check that
amplify.ymlexists in root directory
Repository not connected:
- Go to AWS Console → Amplify → App settings → General → Repository
- Click "Connect repository" and authorize GitHub
Environment variables not working:
- Verify variables are set in stack config
- Check Amplify Console → App settings → Environment variables
- Rebuild the app after changing variables
-
Update GitHub Secrets (if outputs changed):
Go to Settings → Secrets and variables → Actions and update:
NEXT_PUBLIC_COGNITO_USER_POOL_ID→ NewUserPoolIdfrom stack outputsNEXT_PUBLIC_COGNITO_CLIENT_ID→ NewUserPoolClientIdfrom stack outputsNEXT_PUBLIC_AWS_REGION→ AWS region (e.g.,eu-central-1)
Note: If this is your first production deployment, see the "First Time Deployment" section above.
npm run build- Compile TypeScript to JavaScriptnpm run watch- Watch for changes and compilenpm run deploy:dev- Deploy development stacknpm run deploy:prod- Deploy production stacknpm run diff:dev- Show differences for developmentnpm run diff:prod- Show differences for productionnpm run synth:dev- Synthesize CloudFormation template for developmentnpm run synth:prod- Synthesize CloudFormation template for productionnpm run destroy:dev- Destroy development stack (⚠️ dangerous)npm run destroy:prod- Destroy production stack (⚠️ dangerous)
Note: To use a specific AWS profile, set the AWS_PROFILE environment variable or use the --profile flag with npm run cdk.
The Cognito service provides authentication services:
- Cognito User Pool: Manages user authentication
- Cognito User Pool Client: Web application client for frontend
- Email and username sign-in
- Self-registration enabled
- Email verification
- Password policy (min 6 chars, uppercase, lowercase, digits)
- Account recovery via email
- Token validity configuration
Each environment (dev/prod) has its own separate resources:
- Cognito User Pool:
- Development:
mostage-studio-users-dev - Production:
mostage-studio-users-prod
- Development:
- Cognito User Pool Client:
- Development:
mostage-studio-web-client-dev - Production:
mostage-studio-web-client-prod
- Development:
Important: Users in development and production are completely isolated. Changes in one environment do not affect the other.
The API Gateway service provides a REST API for backend Lambda functions:
- API Gateway: REST API endpoint for Lambda functions
- CORS Configuration: Environment-specific CORS configuration
- Rate Limiting: Automatic rate limiting for production
- Lambda Integration: Automatic integration with Lambda functions
GET /unsplash/search: Search for Unsplash imagesPOST /unsplash/download: Track Unsplash image downloads
Development:
- CORS: Allows all origins (
*) for local development - Rate Limit: 1000 requests/second, 2000 burst limit
Production:
- CORS: Restricted to specified domains only (configured via
ALLOWED_ORIGINS) - Rate Limit: 100 requests/second, 200 burst limit
⚠️ Important: You must setALLOWED_ORIGINSin.env.prod.localor the API will reject all CORS requests
The API Gateway is automatically created when deploying the stack. To add new endpoints:
- Create a new Lambda construct in
lib/services/api/your-service/ - Add the Lambda function to the API Gateway in your construct
- Deploy the stack
After deployment, the API Gateway URL is available as a stack output:
ApiUrl: API Gateway base URL (e.g.,https://xxxxxxxxxx.execute-api.eu-central-1.amazonaws.com/dev)
Add this URL to your frontend .env.local as NEXT_PUBLIC_API_URL.
The Unsplash Lambda service provides image search functionality. The Lambda handlers are located in backend/src/lambda/unsplash/:
- Search Function: Lambda function for searching Unsplash images (
backend/src/lambda/unsplash/search.ts) - Download Function: Lambda function for tracking image downloads (
backend/src/lambda/unsplash/download.ts)
Lambda functions are organized in the backend/ directory:
backend/
src/
lambda/
unsplash/
search.ts # Search handler
download.ts # Download tracking handler
This structure allows for:
- Clear separation between infrastructure (CDK) and business logic (Lambda handlers)
- Easy addition of new Lambda functions for other services
- Standard monorepo structure
The Unsplash Access Key is configured separately for development and production environments using .env files.
Security Note: For production, you must configure ALLOWED_ORIGINS to restrict CORS to your frontend domain(s). The API Gateway will reject requests from unauthorized origins.
For Development:
-
Copy the example file:
cd infrastructure cp ../.env.example .env.dev.local -
Edit
.env.dev.localand configure:# Unsplash API Access Key UNSPLASH_ACCESS_KEY=your_dev_access_key_here # CORS (optional for dev, can be empty or ["*"]) ALLOWED_ORIGINS= # Amplify Branch (default: dev) AMPLIFY_BRANCH=dev
-
Deploy:
npm run deploy:dev
For Production:
-
Copy the example file:
cd infrastructure cp ../.env.example .env.prod.local -
Edit
.env.prod.localand configure:# Unsplash API Access Key UNSPLASH_ACCESS_KEY=your_prod_access_key_here # IMPORTANT: Restrict CORS to your frontend domain(s) # Only requests from these domains will be allowed # Example: https://studio.mostage.app,https://www.mostage.app # Or comma-separated: https://studio.mostage.app,https://www.mostage.app ALLOWED_ORIGINS=https://studio.mostage.app # Amplify Branch (default: main) AMPLIFY_BRANCH=main
⚠️ Security Warning: IfALLOWED_ORIGINSis not set or empty, the API Gateway will reject all CORS requests in production. This is intentional for security. -
Deploy:
npm run deploy:prod
Important:
.env.dev.localand.env.prod.localfiles are ignored by git (for security)- Each environment can have a different Unsplash Access Key
- The API key is stored securely in Lambda environment variables after deployment
- Production Security:
- CORS is restricted to specified domains only
- Rate limiting: 100 requests/second, 200 burst limit
- You can also use environment variables directly:
export UNSPLASH_ACCESS_KEY=your_key && npm run deploy:dev
The SES service provides email infrastructure for Cognito (optional):
- SES Email Identity: Verified email address for sending emails
- SES Configuration Set: Optional configuration set for email tracking and analytics
To use SES with Cognito:
-
Verify Email Address (for development):
- Update
lib/stacks/studio-stack.tsto setcreateResources: trueandfromEmail - Deploy the stack
- Verify the email address in AWS SES Console
- Update
-
Verify Domain (for production - recommended):
- Verify your domain in AWS SES Console
- Use verified domain email addresses (e.g.,
noreply@yourdomain.com)
-
Configure Cognito to use SES:
- Update
lib/stacks/studio-stack.tsto setsesFromEmailandsesReplyToEmail - Redeploy the stack
- Update
Important:
- In AWS SES sandbox mode, you can only send emails to verified addresses
- To send to any email address, request production access in SES Console
- SES email identity verification is required before Cognito can send emails
To add a new AWS service (e.g., Lambda, API Gateway, S3):
-
Create a new service directory:
lib/services/your-service/ -
Create
index.tswith your service:import { Construct } from "constructs"; import * as cdk from "aws-cdk-lib"; export interface YourServiceProps { // Define props } export class YourService extends Construct { constructor(scope: Construct, id: string, props: YourServiceProps) { super(scope, id); // Create resources } }
-
Import and use it in
lib/stacks/base.ts:import { YourService } from "../services/your-service"; // In BaseStudioStack constructor: const yourService = new YourService(this, "YourService", { // Pass props });
-
If the service needs environment-specific configuration, add it to
StackConfiginterface and update bothdev.tsandprod.ts. -
Add outputs if needed:
new cdk.CfnOutput(this, "YourServiceId", { value: yourService.id, description: "Your Service ID", exportName: `${environment}-your-service-id`, });
┌─────────────────┐
│ Frontend │
│ (Next.js App) │
└────────┬────────┘
│
│ AWS SDK
│
┌────────▼────────┐
│ Cognito User │
│ Pool │
└─────────────────┘
The frontend communicates directly with AWS Cognito using the AWS SDK. No additional API server is required for authentication operations.
Stack outputs are automatically exported and can be used in other stacks or applications. Outputs include:
UserPoolId- Cognito User Pool IDUserPoolArn- Cognito User Pool ARNUserPoolClientId- Cognito User Pool Client IDUserPoolRegion- AWS Region for Cognito User PoolApiUrl- API Gateway base URL
- Never commit credentials to git
- Use IAM roles instead of access keys when possible
- Rotate credentials periodically
- Use least-privilege IAM policies
- Enable MFA for production accounts
- Review CloudFormation changes before deploying