Skip to content

Repository files navigation

Vision Board (React + Express + Firebase Auth + S3)

Production-leaning full-stack Vision Board app with:

  • React + TypeScript + Vite frontend
  • Express + TypeScript backend (Cloud Run ready)
  • Firebase Auth (Google sign-in)
  • Firebase Admin token verification on backend
  • Firestore for board/card metadata
  • Private AWS S3 bucket with presigned PUT/GET URLs

1) Project Structure

/Users/bennisevich/eventually
  /frontend
    package.json
    tsconfig.json
    tsconfig.app.json
    tsconfig.node.json
    vite.config.ts
    tailwind.config.ts
    postcss.config.cjs
    index.html
    .env.example
    /src
      main.tsx
      App.tsx
      index.css
      /components
        EmptyState.tsx
      /lib
        api.ts
        firebase.ts
      types.ts

  /backend
    package.json
    tsconfig.json
    Dockerfile
    .dockerignore
    .env.example
    /src
      index.ts
      app.ts
      /config
        env.ts
        firebase.ts
        s3.ts
      /lib
        async-handler.ts
        date.ts
        errors.ts
      /middleware
        auth.ts
        error-handler.ts
      /routes
        boards.routes.ts
        storage.routes.ts
      /services
        boards.service.ts
        cards.service.ts
        storage.service.ts
      /types
        express.d.ts
      /validators
        schemas.ts

2) AWS S3 Setup (Private Bucket)

Create bucket

  1. In AWS Console, create an S3 bucket (for example: vision-board-private-assets).
  2. Keep Block all public access = ON.
  3. Do not add public bucket policy.

Bucket CORS configuration

In bucket Permissions -> CORS, set:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "HEAD"],
    "AllowedOrigins": [
      "http://localhost:5173",
      "https://your-frontend-domain.com"
    ],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

IAM policy for backend user/role

Create an IAM user or role used by the backend API and attach least-privilege policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisionBoardS3Access",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::vision-board-private-assets/users/*"
    }
  ]
}

If you want list/debug access from backend, add:

  • s3:ListBucket on arn:aws:s3:::vision-board-private-assets with prefix condition users/.

3) Firebase Setup

Create Firebase project

  1. Go to Firebase Console -> Create project.
  2. In Authentication -> Sign-in method -> enable Google.
  3. In Project settings -> add a Web App and copy:
    • apiKey
    • authDomain
    • projectId
    • appId

Create Firestore

  1. Firebase Console -> Firestore Database -> Create database.
  2. Start in production mode.
  3. Choose region close to Cloud Run region.

Create Firebase service account key (for backend)

  1. Firebase Console -> Project Settings -> Service Accounts.
  2. Generate new private key JSON.
  3. Use these values for backend env:
    • FIREBASE_PROJECT_ID
    • FIREBASE_CLIENT_EMAIL
    • FIREBASE_PRIVATE_KEY (multiline key escaped with \n in .env)

4) Local Environment Variables

Backend (/backend/.env)

Copy /backend/.env.example to /backend/.env and set:

NODE_ENV=development
PORT=8080
FRONTEND_ORIGINS=http://localhost:5173

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
S3_BUCKET_NAME=vision-board-private-assets
PRESIGNED_URL_EXPIRES_SECONDS=900

FIREBASE_PROJECT_ID=...
FIREBASE_CLIENT_EMAIL=...
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

Frontend (/frontend/.env)

Copy /frontend/.env.example to /frontend/.env and set:

VITE_API_BASE_URL=http://localhost:8080
VITE_FIREBASE_API_KEY=...
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=...
VITE_FIREBASE_APP_ID=...

5) Local Development

From /Users/bennisevich/eventually:

Install dependencies

cd backend && npm install
cd ../frontend && npm install

Run backend

cd /Users/bennisevich/eventually/backend
npm run dev

Run frontend

cd /Users/bennisevich/eventually/frontend
npm run dev

Open http://localhost:5173.

6) Backend API Routes (Auth-Protected)

All require:

  • Authorization: Bearer <Firebase ID Token>

Routes:

  • POST /boards
  • GET /boards
  • GET /boards/:id
  • POST /boards/:id/cards
  • GET /boards/:id/cards
  • POST /presign-upload
  • GET /presign-download?key=...

Flow:

  1. Frontend requests POST /presign-upload.
  2. Backend verifies Firebase ID token.
  3. Backend returns presigned PUT URL and key (users/{uid}/boards/{boardId}/{uuid}.jpg|png|...).
  4. Frontend uploads file directly to S3 (media cards only).
  5. Frontend saves card metadata via POST /boards/:id/cards for one of:
    • media (requires imageKey)
    • website (requires websiteUrl)
    • text (requires textContent)
  6. Board card fetch returns presigned GET URLs for media cards and plain metadata for website/text cards.

7) Cloud Run Deployment (Backend)

Build Docker image locally

cd /Users/bennisevich/eventually/backend
docker build -t vision-board-api:latest .

Tag and push image to Artifact Registry

gcloud artifacts repositories create vision-board --repository-format=docker --location=us-central1 --description=\"Vision Board images\"
gcloud auth configure-docker us-central1-docker.pkg.dev
docker tag vision-board-api:latest us-central1-docker.pkg.dev/<GCP_PROJECT_ID>/vision-board/vision-board-api:latest
docker push us-central1-docker.pkg.dev/<GCP_PROJECT_ID>/vision-board/vision-board-api:latest

Deploy to Cloud Run

gcloud run deploy vision-board-api \
  --image us-central1-docker.pkg.dev/<GCP_PROJECT_ID>/vision-board/vision-board-api:latest \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --port 8080 \
  --set-env-vars NODE_ENV=production,PORT=8080,FRONTEND_ORIGINS=https://your-frontend-domain.com,AWS_ACCESS_KEY_ID=...,AWS_SECRET_ACCESS_KEY=...,AWS_REGION=us-east-1,S3_BUCKET_NAME=vision-board-private-assets,PRESIGNED_URL_EXPIRES_SECONDS=900,FIREBASE_PROJECT_ID=...,FIREBASE_CLIENT_EMAIL=...,FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"

Notes:

  • --allow-unauthenticated keeps endpoint publicly reachable for browser clients, but all app routes still enforce Firebase ID token auth.
  • Prefer Secret Manager for sensitive vars (AWS_SECRET_ACCESS_KEY, FIREBASE_PRIVATE_KEY) in production.

Configure frontend to point to Cloud Run

In /frontend/.env set:

VITE_API_BASE_URL=https://vision-board-api-xxxxx-uc.a.run.app

Then build/deploy frontend wherever you host static files.

8) Security Notes

  • AWS credentials are backend-only; never exposed in frontend.
  • S3 bucket stays private.
  • All non-health routes verify Firebase ID token.
  • Board/card ownership checks prevent cross-user access.
  • Input is validated with Zod.
  • Errors are normalized and do not leak internals in production.

9) Cost Considerations

  • Cloud Run: pay per request/CPU/RAM usage; scales to zero for low idle cost.
  • Firestore: pay for document reads/writes/storage; keep payload small and avoid unnecessary polling.
  • S3: pay for storage, PUT/GET requests, and egress; presigned URLs keep architecture simple and cheap.
  • Firebase Auth: Google sign-in in Firebase Auth is typically low-cost/free in many usage tiers.
  • Avoiding ECS/EC2/ALB reduces fixed monthly baseline and ops overhead.

About

Learning how to use AWS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages