A professional portfolio application with React frontend and Node.js backend, featuring enhanced security, performance, and maintainability improvements.
See the /docs folder for detailed documentation:
.env.example)Clone the repository:
git clone https://github.com/tnnrhpwd/portfolio-app.git
cd portfolio-app
Set up environment variables:
cp .env.example .env
# Edit .env with your values
Install dependencies:
npm run install-all
# Or manually:
npm install && cd frontend && npm install && cd ../backend && npm install
Start the development server:
npm start
The application will start on http://localhost:3000.
The application is split across multiple hosting providers, with AWS handling data, storage, and email infrastructure.
| Layer | Service | Role |
|---|---|---|
| Frontend | Netlify | Builds the Vite/React app (frontend/build) via netlify.toml, serves it from Netlify's global CDN, manages security headers, sitemap, and /api/* proxy redirects to the backend. |
| Backend API | Render | Hosts the Node.js/Express server (backend/server.js) at mern-plan-web-service.onrender.com. Handles auth, business logic, Stripe webhooks, and all AWS SDK calls. |
| Database | AWS DynamoDB | Primary NoSQL datastore for users, generic data records, memory/personality entries, and analytics. Accessed via @aws-sdk/lib-dynamodb in backend/services/dataService.js. |
| File Storage | AWS S3 | Stores user uploads (images, OCR documents, attachments) via backend/services/s3Service.js. Files larger than DynamoDB's 400KB limit are offloaded here. |
| CDN for Assets | AWS CloudFront | Fronts the S3 bucket (AWS_CLOUDFRONT_DOMAIN) to deliver user-uploaded media with low latency and cacheable URLs. |
| AWS SES / SMTP (Nodemailer) | Transactional email (password resets, notifications) sent from backend/services/emailService.js. | |
| Payments | Stripe | Subscription billing and webhooks handled in backend/services/stripeService.js. |
| CI/CD | GitHub Actions | Runs tests, security scans (CodeQL), and triggers Netlify/Render deploys on push to master. |
Request flow: Browser β Netlify CDN (static assets + /api/* proxy) β Render-hosted Express API β AWS DynamoDB (data) / S3 + CloudFront (files) / SES (email) / Stripe (payments).
The desktop companion (simple-addon/) is an Electron app that ships independently and talks to the same Render backend.
Environment variables are never committed to this repository.
Templates β backend/.env.example (and backend/config/.env.example) document every supported variable with safe placeholder values. backend/.env is the live, git-ignored file.
Encrypted backups β backend/.env is encrypted with age and pushed to the private tnnrhpwd/portfolio-app-secrets repo as backend.env.age. Only holders of an authorized SSH/age private key can decrypt it.
npm run env:backup # encrypt local .env files and push ciphertext to the secrets repo
npm run env:restore # decrypt and restore .env files from the secrets repo
Recipients β authorized keys live in scripts/env-backup/recipients.txt; the file mapping lives in scripts/env-backup/manifest.json. To authorize a new device:
npm run env:add-recipient -- -PublicKey "C:\path\to\new-device_id_ed25519.pub"
Source of truth (AWS Secrets Manager) β all secrets and app config (Stripe, JWT, DeepSeek, GITHUB_TOKEN, S3 bucket, FROM_EMAIL, ADMIN_USER_ID, β¦) live in one JSON secret (portfolio-app/production) and are hydrated into process.env at boot by backend/utils/awsSecrets.js. Set or rotate any value with:
npm run secret:put -- -Name STRIPE_KEY -Value "sk_live_..."
Is .env still used? Yes β 2 lines. backend/.env holds just the AWS access key pair (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) needed to reach Secrets Manager. On Render, .env is not read β Render injects the same bootstrap creds plus runtime config (AWS_REGION, NODE_ENV, PORT, FRONTEND_URL).
Production (Render) β Render only needs the AWS bootstrap credentials plus runtime config; every secret and app-config value comes from the secret above. See docs/guides/SECRETS_MANAGEMENT.md.
Found a security vulnerability? Please see our Security Policy for responsible disclosure guidelines.
While this project is primarily a personal portfolio, contributions are welcome. If you find a bug or think of a new feature, please feel free to create an issue or a pull request.
This project is open source and available under the MIT License.
1,117 commits
24 commits
JavaScript
69.5%
CSS
16.8%
TypeScript
5.6%
HTML
4.2%
Python
3.7%
A professional portfolio application with React frontend and Node.js backend, featuring enhanced security, performance, and maintainability improvements.
See the /docs folder for detailed documentation:
.env.example)Clone the repository:
git clone https://github.com/tnnrhpwd/portfolio-app.git
cd portfolio-app
Set up environment variables:
cp .env.example .env
# Edit .env with your values
Install dependencies:
npm run install-all
# Or manually:
npm install && cd frontend && npm install && cd ../backend && npm install
Start the development server:
npm start
The application will start on http://localhost:3000.
The application is split across multiple hosting providers, with AWS handling data, storage, and email infrastructure.
| Layer | Service | Role |
|---|---|---|
| Frontend | Netlify | Builds the Vite/React app (frontend/build) via netlify.toml, serves it from Netlify's global CDN, manages security headers, sitemap, and /api/* proxy redirects to the backend. |
| Backend API | Render | Hosts the Node.js/Express server (backend/server.js) at mern-plan-web-service.onrender.com. Handles auth, business logic, Stripe webhooks, and all AWS SDK calls. |
| Database | AWS DynamoDB | Primary NoSQL datastore for users, generic data records, memory/personality entries, and analytics. Accessed via @aws-sdk/lib-dynamodb in backend/services/dataService.js. |
| File Storage | AWS S3 | Stores user uploads (images, OCR documents, attachments) via backend/services/s3Service.js. Files larger than DynamoDB's 400KB limit are offloaded here. |
| CDN for Assets | AWS CloudFront | Fronts the S3 bucket (AWS_CLOUDFRONT_DOMAIN) to deliver user-uploaded media with low latency and cacheable URLs. |
| AWS SES / SMTP (Nodemailer) | Transactional email (password resets, notifications) sent from backend/services/emailService.js. | |
| Payments | Stripe | Subscription billing and webhooks handled in backend/services/stripeService.js. |
| CI/CD | GitHub Actions | Runs tests, security scans (CodeQL), and triggers Netlify/Render deploys on push to master. |
Request flow: Browser β Netlify CDN (static assets + /api/* proxy) β Render-hosted Express API β AWS DynamoDB (data) / S3 + CloudFront (files) / SES (email) / Stripe (payments).
The desktop companion (simple-addon/) is an Electron app that ships independently and talks to the same Render backend.
Environment variables are never committed to this repository.
Templates β backend/.env.example (and backend/config/.env.example) document every supported variable with safe placeholder values. backend/.env is the live, git-ignored file.
Encrypted backups β backend/.env is encrypted with age and pushed to the private tnnrhpwd/portfolio-app-secrets repo as backend.env.age. Only holders of an authorized SSH/age private key can decrypt it.
npm run env:backup # encrypt local .env files and push ciphertext to the secrets repo
npm run env:restore # decrypt and restore .env files from the secrets repo
Recipients β authorized keys live in scripts/env-backup/recipients.txt; the file mapping lives in scripts/env-backup/manifest.json. To authorize a new device:
npm run env:add-recipient -- -PublicKey "C:\path\to\new-device_id_ed25519.pub"
Source of truth (AWS Secrets Manager) β all secrets and app config (Stripe, JWT, DeepSeek, GITHUB_TOKEN, S3 bucket, FROM_EMAIL, ADMIN_USER_ID, β¦) live in one JSON secret (portfolio-app/production) and are hydrated into process.env at boot by backend/utils/awsSecrets.js. Set or rotate any value with:
npm run secret:put -- -Name STRIPE_KEY -Value "sk_live_..."
Is .env still used? Yes β 2 lines. backend/.env holds just the AWS access key pair (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) needed to reach Secrets Manager. On Render, .env is not read β Render injects the same bootstrap creds plus runtime config (AWS_REGION, NODE_ENV, PORT, FRONTEND_URL).
Production (Render) β Render only needs the AWS bootstrap credentials plus runtime config; every secret and app-config value comes from the secret above. See docs/guides/SECRETS_MANAGEMENT.md.
Found a security vulnerability? Please see our Security Policy for responsible disclosure guidelines.
While this project is primarily a personal portfolio, contributions are welcome. If you find a bug or think of a new feature, please feel free to create an issue or a pull request.
This project is open source and available under the MIT License.
1,117 commits
24 commits
JavaScript
69.5%
CSS
16.8%
TypeScript
5.6%
HTML
4.2%
Python
3.7%