conduit-full — system architecture

One Express app behind a dev proxy. No microservices — a monolith done carefully.

In production, Express serves the built frontend directly (express.static). In development, Vite's dev server owns port 3000 and proxies every /api request to Express on port 3001 — the frontend never talks to Postgres, and the backend never renders anything. See Auth & Request Flow for how a single request actually moves through the middleware chain.

Request Path

Browser
↓ HTTP
Vite dev server (:3000) — proxies /api →
Express app (:3001) — cors → json → rate limiter on /api
↓ mounted in order
/api/users, /api/user register, login, current user
/api/articles CRUD, comments, favorites, feed
/api/profiles, /api/tags follow/unfollow, tag list
↓ Sequelize
PostgreSQL

Layers

In progress

frontend/

  • React 19, Vite + SWC, React Router
  • TypeScript from file one — no JS phase
  • 12 of ~16 API service modules done
Complete

backend/routes + controllers

  • 5 route groups, all auth-guarded per-endpoint
  • verifyToken is soft-auth by default
  • Global rate limiter on every /api route
Complete

backend/models

  • 4 models, real migrations as schema source of truth
  • No sequelize.sync({ alter: true }) at boot
i

verifyToken is the one piece of middleware every route touches, and it's deliberately permissive: no Authorization header just means the request proceeds anonymously, not a 401 — each controller decides for itself whether it actually requires a logged-in user. That distinction is the source of a real bug found while typing the frontend's getComments service; see the Auth & Request Flow diagram for the full trace.