PROJECT 4 LIVE

AI Developer Portfolio Platform

A production portfolio platform built through iterative Claude Code sessions — a collaborative process where I acted as product manager and Claude Code as engineer. Four microservices running live: public website, CMS admin panel, analytics service, and newsletter service. Deployed on Railway with Cloudflare CDN, custom domain, and PostgreSQL.

Node.js 18ExpressPostgreSQLRailwayCloudflare CDNResend APIEJS TemplatesDockerHTML/CSS
GitHub

SCREENSHOTS

Screenshot 1
Screenshot 2
Screenshot 3
Screenshot 4

Research Question

Can Claude Code iteratively build and deploy a production-grade portfolio platform through a collaborative feedback loop — with a human as product manager and Claude as engineer?

Video Walkthrough

Key Results

  • 4 microservices deployed and running on Railway

  • 1 custom domain configured (balajiloganathan.net)

  • SSL certificate provisioned automatically via Cloudflare

  • Starting hosting cost: $5/month (Railway Hobby plan)

  • Newsletter email delivered to inbox using Resend + verified domain

  • Project screenshots gallery with lightbox carousel

  • 15 theme presets + 6 background styles, all CMS-controlled

  • Zero manual coding — fully built through iterative Claude Code sessions

  • Foundation for the entire Daily Microservices Challenge series

Architecture

External Users (browsers)
        ↓ HTTPS (Cloudflare CDN + SSL + DDoS protection)
   balajiloganathan.net
        ↓
   Railway: WEBSITE SERVICE (public, port 8080)
        ↓ HTTP — Railway private network
   ├─ cms.railway.internal:8080        → CMS Service
   │     └─ PostgreSQL (shared)
   ├─ analytics.railway.internal:8080  → Analytics Service
   │     └─ PostgreSQL (shared)
   └─ newsletter.railway.internal:8080 → Newsletter Service
         └─ PostgreSQL (shared)
              ↓
   PostgreSQL (Railway managed, single instance)
Service Exposure Port Database
Website Public 8080 —
CMS Private 8080 PostgreSQL
Analytics Private 8080 PostgreSQL
Newsletter Private 8080 PostgreSQL

Case Study

The Problem

After three daily microservices experiments, the work had no home. Everything lived on GitHub — no public showcase, no way to update content without touching code, no visitor analytics, no way to build an audience around what was being built.

The question for Day 04 was whether Claude Code could build the infrastructure itself. Not just prototype code — a real production platform with a custom domain, SSL, a working CMS, and live deployment to Railway cloud.

What followed wasn't a single session. It was an iterative process: build, test, break, feedback, fix, repeat. The result is balajiloganathan.net — a platform that now hosts the entire Daily Microservices Challenge series.

What Was Built

Four microservices, each with a single responsibility:

  • Website Service — The public-facing portfolio at balajiloganathan.net. Renders project case studies, an about page, and a research updates feed. Dark theme with 15 presets and 6 background styles — all controlled from the CMS with no code changes required.
  • CMS Service — A full admin panel for managing all site content. Projects, case studies, site settings, profile photo, and updates — all editable through a browser interface. No code changes needed to add a new project or update existing content.
  • Analytics Service — Visitor tracking with a private dashboard. Tracks page views, traffic sources, device types, and peak hours. Data stored in PostgreSQL, visible only to the admin.
  • Newsletter Service — Subscriber management with email delivery via Resend API. Includes a subscriber list, bulk send capability, test email, and unsubscribe flow. Emails land in inbox, not spam, using a verified sending domain.

Architecture

All four services run on Railway's private network. Only the website service is exposed to the internet. The CMS, analytics, and newsletter services communicate internally via Railway private domains on port 8080 — faster than public routing, more secure, and free of bandwidth charges.

Cloudflare sits in front of the website service as CDN, DDoS protection, and SSL termination. DNS routes through Cloudflare's nameservers, enabling CNAME flattening at the root domain. Railway's Cloudflare integration handled the entire SSL and domain configuration in under 5 minutes.

How It Was Actually Built

The build process wasn't a single prompt → working system. It was closer to being a product manager with an AI engineer: describe what was needed, review what Claude Code produced, test it on the live Railway deployment, identify what broke, give specific feedback, and repeat.

This iterative loop — build → deploy → test → feedback → fix — is what actually produced a working production system. The key insight: Claude Code handles implementation decisions well. The human handles product decisions — what should exist, how it should behave, whether it actually works in production.

Issues Encountered

  • Silent internal service failures — The website appeared to work but all calls to internal Railway services were silently failing. Node's http module couldn't handle Railway's HTTPS + port 8080 combination — errors were swallowed without logging. Switching to Node 18 native fetch() and adding explicit error logging to every catch block revealed four separate failures that had been completely invisible.
  • PayloadTooLargeError on screenshot uploads — Uploading project screenshots through the CMS admin panel failed with no useful error message. Express has a default 100kb body limit — easy to miss, hard to diagnose. Base64-encoded images exceed this easily. Fixed with one line: express.json({ limit: '50mb' }).
  • CSS not updating on the live site — Style changes were deployed but not reflecting for visitors. Cloudflare was caching the old stylesheet. Fixed with a cache-busting query string: styles.css?v=2. Minor fix but took time to diagnose because the problem only appeared on the live URL, not locally.
  • Missing Railway environment variables — The analytics and newsletter admin panels returned 404 and 500 errors after deployment. No code bug — ADMIN_SECRET and ADMIN_EMAIL had not been set as Railway environment variables. Fixed without any code changes, purely through the Railway dashboard.

Session Summary

Day 04 produced a live production platform that now serves as the backbone of the Daily Microservices Challenge series. Every project case study, every update, every site setting is managed through the CMS — no code changes required to add new content.

The process proved something more interesting than "Claude Code can build a portfolio." It proved that the right model for building production systems with AI is iterative and collaborative — human as product manager, Claude as engineer. Neither works as well without the other.

The platform became the foundation every subsequent project builds on — a living system that grows with the series, not a static page that gets outdated.

Key Findings & Learnings

  • Building production software with Claude Code is a product management exercise, not a coding exercise. The process that worked: describe the problem clearly → review what Claude built → test it live → give specific feedback on what broke → repeat. Claude handles implementation; you handle product decisions. Trying to get it right in one prompt doesn't work — iteration is the method.


  • The website appeared to work but all internal API calls were silently failing. Node's http module couldn't handle Railway's HTTPS + port 8080 combination — it swallowed errors without logging. Switching to Node 18 native fetch() and adding explicit error logging to every catch block revealed four separate failures that had been completely invisible. Silent catch blocks are the number one cause of mysterious production failures.


  • Railway's private networking requires explicit port declarations. Internal services must be called as service-name.railway.internal:8080 — not just the service name, not via public URLs. Getting this wrong means either services can't reach each other or internal traffic routes over the public internet unnecessarily. Once configured correctly, internal calls are faster, cheaper, and don't count against bandwidth limits.


  • Uploading project screenshots through the CMS broke with a PayloadTooLargeError — no other explanation. Express has a default 100kb body limit that's easy to miss. Base64-encoded images are significantly larger than their file size. One line fixed it: express.json({ limit: '50mb' }). Any file upload flow through Express needs an explicit body limit or it will fail silently in production.


  • Configuring a custom domain with SSL on Railway through Cloudflare is genuinely one click once you understand the correct sequence: add the domain in Railway, copy the CNAME record, add it in Cloudflare, enable the Railway Cloudflare integration. The entire process took under 5 minutes. Railway's Cloudflare integration handles SSL automatically — no manual certificate configuration needed.


  • Building the CMS as a separate microservice rather than hardcoding content into the website service was the right call. Every project, every case study, every site setting is managed through an admin panel — no code changes needed to update the portfolio. As the series grew, adding new entries took minutes, not hours. The CMS became the backbone of the entire series.
Personal technical research experiment. All observations are from controlled testing environments. Not financial advice.
● Live AI Trading
PortfolioLoading…
P&L—
Trades—
Open Dashboard →