A production-grade monorepo portfolio platform built with PERN stack (PostgreSQL, Express, React, Node.js) + TypeScript, Redis caching, BullMQ job queue, and admin dashboard.
- Backend: Node.js 20 + Express.js + TypeScript (strict mode)
- Database: PostgreSQL with Prisma ORM
- Caching & Sessions: Redis with ioredis
- Frontend: React 18 + Vite + TypeScript
- Admin Dashboard: React 18 + Vite + TypeScript (with JWT auth)
- Styling: Tailwind CSS with CSS variables for theming
- Validation: Zod schemas on all boundaries
- Background Jobs: BullMQ with Redis queue
- Email: Nodemailer with SMTP
- API Documentation: Swagger/OpenAPI 3.0
- Rate Limiting: express-rate-limit with Redis store
- Logging: Pino (structured) + Morgan (HTTP)
- Containerization: Docker multi-stage builds + docker-compose
portfolio-platform/
βββ backend/ # Node.js API server
β βββ src/
β β βββ config/ # Environment, DB, Redis, Mail configs
β β βββ middleware/ # Auth, rate-limit, error handling
β β βββ services/ # Cache, mail services
β β βββ jobs/ # BullMQ queue and workers
β β βββ utils/ # Helpers, error codes, logger
β β βββ modules/ # Feature modules (8 total)
β β β βββ auth/ # JWT, bcryptjs, token rotation
β β β βββ blog/ # CRUD with Redis caching
β β β βββ articles/ # CRUD with Redis caching
β β β βββ books/ # CRUD with pricing
β β β βββ career/ # Timeline with type enums
β β β βββ achievements/
β β β βββ downloads/ # Counter tracking
β β β βββ contact/ # Form submission + email
β β β βββ newsletter/ # Subscribe/unsubscribe
β β βββ routes/v1/ # API route aggregation
β β βββ docs/ # Swagger setup
β β βββ app.ts # Express app factory
β β βββ server.ts # Entry point
β βββ prisma/
β β βββ schema.prisma # Database schema (11 models)
β β βββ seed.ts # Initial data seeding
β βββ Dockerfile # Multi-stage build
β
βββ frontend/ # Public portfolio website
β βββ src/
β β βββ lib/ # Axios instance, React Query client
β β βββ routes/ # 11 lazy-loaded routes
β β βββ features/ # Feature hooks (blog, articles, etc.)
β β βββ pages/ # Page components
β β βββ components/ # Reusable UI components
β β βββ types/ # TypeScript interfaces
β β βββ utils/ # Utilities
β β βββ App.tsx # Root component
β β βββ main.tsx # React entry point
β β βββ index.css # Tailwind directives
β βββ Dockerfile # Multi-stage build
β
βββ dashboard/ # Admin panel
β βββ src/
β β βββ lib/ # Axios instance, React Query client
β β βββ routes/ # Protected routes + login
β β βββ pages/ # Admin pages (CRUD, messages)
β β βββ components/ # Admin UI components
β β βββ types/
β β βββ utils/
β β βββ App.tsx
β β βββ main.tsx
β β βββ index.css
β βββ Dockerfile # Multi-stage build
β
βββ docker-compose.yml # Multi-service orchestration
βββ .dockerignore # Docker build exclude patterns
βββ .github/workflows/ci.yml # GitHub Actions CI pipeline
| Module | Features | Rate Limit |
|---|---|---|
| auth | Register, login, refresh token, logout | 5 req/min |
| blog | CRUD blogs. Cached: lists (5min), posts (10min) | 100 req/min |
| articles | CRUD articles. Same caching as blog | 100 req/min |
| books | CRUD books with pricing (Decimal type) | 100 req/min |
| career | Timeline entries with type enum (JOB, EDUCATION, etc.) | 100 req/min |
| achievements | Badge/achievement tracking | 100 req/min |
| downloads | Resource management with counter | 100 req/min |
| contact | Form submission with admin email | 3 req/hour |
| newsletter | Subscribe/unsubscribe with welcome email | 100 req/min |
- Node.js 20+ (or Docker)
- PostgreSQL 16+
- Redis 7+
- npm 10+ or pnpm
-
Clone and setup root:
git clone <repo> cd portfolio-platform npm install
-
Environment setup:
cp .env.example .env # Edit .env with your PostgreSQL, Redis, SMTP credentials -
Database migration:
npm run db:migrate --workspace=backend npm run db:seed --workspace=backend
-
Start all services (concurrent):
npm run dev
Or start individually:
npm run dev --workspace=backend # http://localhost:4000 npm run dev --workspace=frontend # http://localhost:3000 npm run dev --workspace=dashboard # http://localhost:3001
-
Access the stack:
- Frontend: http://localhost:3000
- Dashboard: http://localhost:3001 (login required)
- API Docs: http://localhost:4000/api/docs
-
Build and start all services:
docker-compose up -d
First-time only, run migrations:
docker-compose exec backend npm run db:migrate docker-compose exec backend npm run db:seed
-
Access:
- Frontend: http://localhost:3000
- Dashboard: http://localhost:3001
- API: http://localhost:4000
- Postgres: localhost:5432 (user:
portfolio, password:portfolio_dev) - Redis: localhost:6379
-
View logs:
docker-compose logs -f backend docker-compose logs -f frontend docker-compose logs -f dashboard
-
Shutdown gracefully:
docker-compose down
# Type check all workspaces
npm run typecheck
# Lint all workspaces
npm run lint
# Test backend
npm run test --workspace=backend
# Build all workspaces
npm run build
# Build Docker images
docker-compose buildRegister:
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure123"
}Login:
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure123"
}Response:
{
"success": true,
"data": {
"accessToken": "jwt...",
"refreshToken": "jwt...",
"user": { "id": "...", "email": "..." }
}
}Refresh Token:
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refreshToken": "jwt..."
}List blogs (paginated):
GET /api/v1/blog?page=1&limit=10Get blog by slug:
GET /api/v1/blog/:slugFor admin: All CRUD operations at POST, PUT, DELETE /api/v1/blog/:id require JWT admin token.
Swagger UI available at: http://localhost:4000/api/docs
- JWT Authentication: Short-lived access tokens (15min) + long-lived refresh tokens (7d)
- Password Hashing: bcryptjs with 12 salt rounds
- Rate Limiting: Graduated limits (100/min general, 5/min auth, 3/hr contact)
- CORS: Configurable origin whitelist
- Helmet: Security headers on all HTTP responses
- Input Validation: Zod schemas on every endpoint
- Idempotency Keys: 30s cache on POST requests to prevent duplicates
- Error Handling: Centralized middleware, no stack traces in production
Create .env file from .env.example:
# Critical
DATABASE_URL=postgresql://user:pass@localhost/portfolio_dev
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key
JWT_REFRESH_SECRET=your-refresh-secret
# Email (Gmail example)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
ADMIN_EMAIL=admin@portfolio.dev
# CORS
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
# Frontend
VITE_API_URL=http://localhost:4000/apiEdit frontend/tailwind.config.ts and dashboard/tailwind.config.ts:
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
},
fontFamily: {
display: 'var(--font-display)',
},
},
}CSS variables in src/index.css:
:root {
--color-primary: #3b82f6;
--font-display: "Inter", sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #1e40af;
}
}- User: Profiles, roles (USER, ADMIN), token tracking
- Blog: Post content, slug-based routing, view counting
- Article: Similar to Blog, separate table for organization
- Book: Metadata, pricing (Decimal), file URLs
- CareerTimeline: Type enum (JOB, EDUCATION, etc.), date range
- Achievement: Badges, date tracking
- Download: Resource tracking with counter
- Contact: Form submissions, read status, admin replies
- NewsletterSubscriber: Email list with unsubscribe tracking
- RefreshToken: JWT revocation, device tracking
- AuditLog: Admin action tracking (optional, not in initial schema)
All models use:
- UUID primary keys
createdAt/updatedAttimestamps- Proper indexes for common queries (slug, email, etc.)
Enable debug mode:
DEBUG=portfolio:* npm run dev --workspace=backendInstall React Developer Tools browser extension, then:
npm run dev --workspace=frontendView Prisma Studio:
npm run prisma:studio --workspace=backend-
Build images:
docker-compose build
-
Push to registry (optional):
docker tag portfolio-backend:latest your-registry/portfolio-backend:latest docker push your-registry/portfolio-backend:latest
-
Deploy (see your infrastructure provider's docs for:
- Kubernetes YAML
- AWS ECS task definitions
- Heroku procfile
- DigitalOcean app spec
Use a secure secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) and inject:
DATABASE_URL=postgresql://prod_user:prod_pass@prod-db:5432/portfolio
REDIS_URL=redis://:password@prod-redis:6379
JWT_SECRET=<random-64-char-key>
SMTP_HOST=smtp.sendgrid.net
# ... others- Fork and create feature branch:
git checkout -b feature/my-feature - Make changes following the architecture patterns
- Run linting and type checks:
npm run lint && npm run typecheck - Test your changes:
npm run test - Push and create a pull request
MIT License - see LICENSE file for details
Edeh Chinedu Daniel - Portfolio & Contact Platform
Questions or issues? Open an issue on GitHub or contact admin@portfolio.dev