Alex Herlan Portfolio
A custom React and FastAPI portfolio built from Alex Herlan's résumé. The interface uses hand-written CSS—no Tailwind or component library. Profile, career, and skill content comes from JSON; journal articles use Supabase through FastAPI with a local JSON fallback.
Project structure
backend/
data/ Local content and article fallback
supabase/schema.sql Supabase articles table and security setup
seed_supabase.py One-time migration for the ten starter articles
main.py FastAPI routes, SMTP delivery, and production serving
requirements.txt
frontend/
public/ Alex's locally stored profile image
src/ React pages, components, and custom CSS
Alexander Herlan Resume 2024.pdf
Alexander Herlan Resume 2024.md
Run locally
Create and install the Python environment from the repository root:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r backend\requirements.txt
Start FastAPI:
uvicorn backend.main:app --reload --port 8000 --env-file backend\.env
In a second terminal, start React:
cd frontend
npm install
npm run dev
Open http://localhost:5173. Vite proxies /api requests to FastAPI on port 8000.
The interface loads Roboto Flex from Google Fonts for its compact UI copy, while editorial headings keep the résumé-inspired serif treatment.
Contact-form delivery
The form uses POST /api/contact and sends email through SMTP. Copy backend/.env.example to backend/.env, fill in the SMTP values, and start the API with the environment file:
uvicorn backend.main:app --reload --port 8000 --env-file backend\.env
If SMTP is unavailable, the API returns a clear delivery error and the page keeps Alex's direct email, LinkedIn, and GitHub links visible.
Journal publisher
Open http://localhost:5173/blog/manage and sign in with the journal admin password. The writing room uses Tiptap for headings, bold and italic text, lists, undo/redo, and inline font sizes. Publishing writes to Supabase when it is configured, or to backend/data/posts.json during local fallback mode.
Set unique production values in backend/.env:
JOURNAL_ADMIN_PASSWORD=replace-with-a-strong-password
JOURNAL_TOKEN_SECRET=replace-with-a-long-random-secret
The login endpoint returns a signed session that expires after four hours. The password remains server-side and the browser stores only the temporary token.
Supabase article storage
The runtime backend needs two values from Supabase Dashboard → Settings → API Keys:
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_SECRET_KEY=sb_secret_replace_me
SUPABASE_ARTICLES_TABLE=articles
Use the new sb_secret_... key when available. It bypasses Row Level Security and must stay only in backend/.env; never add it to React or commit it. The older service_role key is accepted as a compatibility fallback through SUPABASE_SERVICE_ROLE_KEY.
To connect a project without creating anything manually in the Supabase console:
- Add the URL, secret key, project ref, and scoped access token to
backend/.env. - Run
bash scripts/setup_supabase.shfrom the repository root. - Restart FastAPI.
The Bash command applies backend/supabase/schema.sql through the Management API and imports all ten starter articles. Use bash scripts/setup_supabase.sh --schema-only when you want the table without seed data.
The access token must be project-scoped with Database: Read-write permission. Supabase recommends scoped access tokens for agents and automation because their reach can be limited to one project. Do not paste database passwords, access tokens, or secret keys into chat.
Content API
GET /api/profileGET /api/experienceGET /api/skillsGET /api/postsGET /api/posts/{slug}POST /api/auth/loginGET /api/auth/sessionPOST /api/posts(authenticated)GET /api/resumePOST /api/contact
Edit the files in backend/data to update profile, career, and skill content. The health endpoint reports articles: supabase when the remote journal store is connected and articles: json-fallback otherwise.
Production build
cd frontend
npm run build
cd ..
uvicorn backend.main:app --host 0.0.0.0 --port 8000
When frontend/dist exists, FastAPI serves the built single-page application and supports direct links such as /experience and /blog/{slug}.