A fullstack portfolio website built with React, TypeScript, Express, and PostgreSQL. Designed to showcase real production experience from Google Summer of Code, Inkscape contributions, and software engineering internships.
| Layer | Technology |
|---|---|
| Frontend | React + TypeScript (Vite) |
| Styling | Tailwind CSS v3 |
| Backend | Node.js + Express + TypeScript |
| Database | PostgreSQL 16 |
| ORM | Prisma 5 |
| API Style | REST |
- Node.js v18+
- PostgreSQL 16
- npm
git clone https://github.com/rafay/rafay-portfolio.git
cd rafay-portfolio# Start PostgreSQL (macOS with Homebrew)
brew services start postgresql@16
# Create the database
createdb rafay_portfoliocd backend
# Install dependencies
npm install
# Configure environment variables
cp .env.example .env
# Edit .env with your PostgreSQL connection string
# Run database migrations
npx prisma migrate dev
# Seed the database with portfolio content
npx ts-node --compiler-options '{"lib":["ES2020"],"types":["node"],"module":"commonjs","esModuleInterop":true}' prisma/seed.ts
# Start the backend server
npm run dev
# Server runs on http://localhost:3001cd frontend
# Install dependencies
npm install
# Start the dev server
npm run dev
# App runs on http://localhost:5173All endpoints return JSON. Error responses follow the shape { error: string, status: number }.
Returns all projects, optionally filtered by tag.
Query Parameters:
tag(optional) — filter by technology (e.g.,?tag=Python)
Example Response:
[
{
"id": 1,
"title": "SmartEngage",
"description": "Full-stack ML platform...",
"tags": ["Python", "FastAPI", "XGBoost"],
"githubUrl": "#",
"liveUrl": null,
"featured": true,
"order": 0,
"createdAt": "2026-06-26T22:54:46.000Z"
}
]Returns a single project by ID.
Status Codes: 200 success, 404 not found, 400 invalid ID
Returns all work experience sorted by order.
Example Response:
[
{
"id": 1,
"company": "Google Summer of Code",
"role": "Open Source Developer",
"location": "Remote",
"startDate": "May 2025",
"endDate": "Sep 2025",
"bullets": ["Owned a complex deliverable..."],
"order": 0
}
]Returns skills grouped by category.
Example Response:
[
{
"category": "Languages",
"skills": ["C++", "JavaScript", "TypeScript", "Python", "Java"]
},
{
"category": "Frontend",
"skills": ["React", "Next.js", "Tailwind CSS", "HTML5/CSS3"]
}
]Saves a contact form submission.
Request Body:
{
"name": "Jane Doe",
"email": "jane@example.com",
"subject": "Job Opportunity",
"message": "Hi Rafay, I'd like to discuss..."
}Response: 201 Created
{
"success": true,
"id": 1
}Status Codes: 201 created, 400 validation error, 500 server error
Downloads the CV as a PDF file with Content-Disposition: attachment.
Status Codes: 200 success, 404 file not found
Portfolio/
├── frontend/ # React + TypeScript (Vite)
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── pages/ # Page-level section components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── types/ # Shared TypeScript interfaces
│ │ └── App.tsx # Main application
│ ├── tailwind.config.js # Tailwind CSS configuration
│ └── vite.config.ts # Vite config with API proxy
│
├── backend/ # Node.js + Express + TypeScript
│ ├── src/
│ │ ├── routes/ # Express route handlers
│ │ ├── controllers/ # Business logic
│ │ ├── middleware/ # Logger & error handler
│ │ ├── types/ # TypeScript interfaces
│ │ └── index.ts # Entry point
│ ├── prisma/
│ │ ├── schema.prisma # Database schema
│ │ └── seed.ts # Database seed script
│ ├── assets/
│ │ └── cv.pdf # CV file (replace with actual)
│ └── .env.example # Environment variable template
│
├── .prettierrc # Code formatting config
└── README.md
The frontend and backend are completely separate applications:
- Frontend (port 5173): React SPA that fetches all data from the backend API. Vite proxies
/apirequests to the backend during development. - Backend (port 3001): Express REST API connected to PostgreSQL via Prisma ORM. Serves data and handles contact form submissions.
This separation demonstrates the fullstack mental model — frontend and backend communicate exclusively through HTTP/REST.
MIT