Skip to content

Template v2: fullstack frontend/ + backend/ (Vite/React 19/TS + rendered FastAPI backend) - #6

Merged
albertotb merged 11 commits into
mainfrom
claude/npm-supply-chain-hardening
Aug 10, 2026
Merged

Template v2: fullstack frontend/ + backend/ (Vite/React 19/TS + rendered FastAPI backend)#6
albertotb merged 11 commits into
mainfrom
claude/npm-supply-chain-hardening

Conversation

@albertotb

@albertotb albertotb commented Aug 8, 2026

Copy link
Copy Markdown
Member

What

Turns the repo into a fullstack template: a modernized React frontend (frontend/) paired with a FastAPI backend (backend/) rendered from python-copier-template, plus the glue (compose, CI, contract test) that ties them together. Grew from the original scope (npm supply-chain hardening → frontend modernization → fullstack restructure) — commits are staged for review in that order.

Layout & ownership model

frontend/   React 19 + TypeScript + Vite 8 + MUI v9 + TanStack Query   (repo-owned)
backend/    FastAPI example rendered from python-copier-template       (bot-owned mirror)
root        README, Makefile, compose.yaml, workflows, dependabot      (repo-owned)
  • backend/ is a rendered mirror — never edited here. A sync job in python-copier-template (spec + ready-to-copy workflow in docs/backend-sync.md) re-renders it and opens an auto-merging PR on template changes, python-template style. .copier-answers.yml is kept so projects created from this template can run copier update on their own diverged backends.
  • Deterministic post-render adjustments (documented for the sync job): drop rendered .github/ (workflows are repo-owned at root), set [tool.setuptools_scm] root = ".." (version derives from this repo's git metadata).

Frontend (modernized from CRA)

  • CRA (deprecated, vulnerable react-scripts 4) → Vite 8; React 17 → 19; strict TypeScript; MUI v4 → v9; react-router v5 → v7; TanStack Query v5; npm audit: 0 vulnerabilities
  • Example page wired to the real backend contract: GET /api/ → status/version chip, POST /api/predict → mutation demo (typed wrappers in src/api/backend.ts)
  • Vite dev proxy /apilocalhost:7000 (prefix stripped, no CORS); nginx does the same in prod
  • ESLint 10 flat config + Prettier, Vitest + Testing Library, multi-stage Dockerfile (Node 24 build → unprivileged nginx, non-root, ~84 MB)
  • Node 24 LTS (.nvmrc), .npmrc supply-chain hardening (min-release-age cooldown + ignore-scripts — the original PR, kept), single npm lockfile
  • react-dropzone example removed (no matching endpoint in the template API; in git history if needed)

Backend

Rendered with package_name=app, include_api=true, include_docker=true, include_cli=false. uv, ruff, ty, pytest, prek config, uv.lock generated + sync-with-uv, own Dockerfile (uvicorn on port 80).

Glue

  • compose.yaml: docker compose up --build → nginx serves the frontend on :8080 and proxies /api to the backend container
  • CI (path-filtered): Frontend (lint/format/test/build), Backend (ruff/format/ty/pytest), Contract — boots the real backend and exercises the endpoints the frontend uses; this check is what makes automated backend syncs safe to auto-merge
  • Root Makefile (delegating targets), fullstack README, CLAUDE.md with the ownership rules, Dependabot (actions + npm; backend deps come via template sync)

⚠️ Two upstream bugs found in python-copier-template (also affect python-template)

Patched locally with TEMPORary markers; both must be fixed upstream before enabling the sync workflow (a sync would revert the patches — details in docs/backend-sync.md):

  1. Rendered Dockerfile can't build: pinned uv:0.10.12-python3.14-bookworm-slim base doesn't exist (Python 3.14 uv images ship on trixie). Patched to trixie.
  2. Missing PyYAML: uvicorn needs it for --log-config log_conf.yaml; both the Docker CMD and the python app/api.py dev entrypoint crash without it. Patched by adding pyyaml.

Verification (all local)

  • Frontend: lint, format check, Vitest, build ✅ · Backend: ruff, ruff format, ty, pytest, deptry ✅
  • Dev flow: Vite proxy → live uvicorn round-trip ✅
  • Full compose stack in a headless browser: status chip shows "Backend online (version …)", predict round-trip through nginx returns the result ✅

Follow-ups after merge

🤖 Generated with Claude Code

https://claude.ai/code/session_011texLkDBELWbXsBf6San3M

claude added 2 commits August 8, 2026 13:11
- min-release-age=3: refuse dependency versions younger than 3 days so
  short-lived malicious releases (recent npm worms were yanked within
  hours) are never resolved. npm ci from the lockfile is unaffected.
  Requires npm >= 11.10.0; older npm ignores the key harmlessly.
- ignore-scripts=true: neutralize the preinstall-hook execution vector.
  This CRA project has no dependency that needs an install script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5mtFwwNP8tzKBC74X7rQk
Full rewrite of the template on top of the supply-chain hardening:

- Replace deprecated Create React App (react-scripts 4) with Vite 8
- React 17 -> 19, Material-UI v4 -> MUI v9, react-router v5 -> v7,
  react-dropzone v11 -> v20; npm audit now reports 0 vulnerabilities
- Migrate source to TypeScript (strict), sx-based styling, createRoot
- Add TanStack Query data layer with a typed fetch wrapper and example
  health-check query + file-upload mutation against a Python backend
- Dev proxy for /api -> localhost:8000, .env.example with VITE_API_URL
- ESLint 10 flat config + Prettier, Vitest + Testing Library with an
  example test, GitHub Actions CI (lint, format, test, build)
- Rewrite README for the template, single npm lockfile (drop yarn.lock),
  .nvmrc (Node 22), remove CRA/PWA leftovers and generic demo content

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
@albertotb albertotb changed the title Harden npm supply chain: install-layer cooldown + no install scripts Modernize template: Vite, React 19, TypeScript, MUI v9 + supply-chain hardening Aug 8, 2026
claude added 7 commits August 8, 2026 19:13
Node 24 has been the active LTS since Oct 2025; Node 22 is in
maintenance. CI picks it up via .nvmrc; engines allows >=22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Build stage on node:24-alpine with layer-cached npm ci (BuildKit
  cache mount) and VITE_API_URL build arg
- Runtime stage on nginxinc/nginx-unprivileged (non-root, port 8080)
  serving the static build: SPA fallback for React Router, immutable
  caching for hashed assets, no-cache for index.html, gzip, healthcheck,
  commented /api proxy block for the backend
- .dockerignore and README section

Verified locally: image builds, container serves / and /about (SPA
fallback), cache headers correct, runs as uid 101, healthcheck healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Prepares the symmetric frontend/ + backend/ fullstack layout. Pure move,
no content changes; CI workflow paths are updated in a follow-up commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Rendered with copier (answers in backend/.copier-answers.yml: package
app, FastAPI API + Docker, no CLI) plus two deterministic post-render
steps that the future sync job must reproduce:

- drop the rendered .github/ (workflows are repo-owned at root)
- set [tool.setuptools_scm] root = ".." so the version derives from
  this repo's git metadata (pyproject is not at the git root here)

uv.lock generated and .pre-commit-config.yaml revs aligned with
sync-with-uv, mirroring the python-template sync job. The answers file
is kept so projects created from this template can run `copier update`
on their own backends.

backend/ is a mirror of the template: do not edit it in this repo —
changes belong in python-copier-template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Status chip reads GET / (shows backend version); mutation example is
  POST /predict, matching backend/app/api.py
- Vite dev proxy targets localhost:7000 and strips the /api prefix;
  nginx does the same (nginx.compose.conf variant with proxy enabled,
  used by the root compose.yaml)
- Typed endpoint wrappers in src/api/backend.ts
- Remove react-dropzone example (no matching backend endpoint in the
  template API; easy to restore from git history if needed)
- Frontend README scoped to the frontend; fullstack docs move to root

Verified end-to-end: dev proxy against live uvicorn returns version and
prediction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Fullstack README (structure, ownership model, quickstart) and
  CLAUDE.md documenting that backend/ is a rendered mirror
- Makefile with delegating targets for both sides
- compose.yaml: backend + frontend, nginx /api proxy via compose-
  specific config mount
- Path-filtered workflows: Frontend (lint/format/test/build), Backend
  (ruff/ty/pytest), Contract (boots the real backend and checks the
  endpoints the frontend uses — the gate that makes automated backend
  syncs safe to auto-merge)
- Dependabot: actions + npm (backend deps come via template sync)
- docs/backend-sync.md: deterministic render recipe and the ready-to-
  copy sync workflow for python-copier-template

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Both must be fixed in python-copier-template before enabling the backend
sync workflow (a sync would revert these local patches). Both also
affect python-template itself:

- Dockerfile: pinned uv:0.10.12-python3.14-bookworm-slim base does not
  exist (python 3.14 uv images ship on trixie); switch builder and
  runtime to trixie
- pyproject: add pyyaml — uvicorn requires it to load log_conf.yaml, so
  the Docker CMD and the `python app/api.py` dev entrypoint crash
  without it (plus deptry DEP002 ignore, uv.lock update)

Verified after patching: docker compose stack serves the frontend on
:8080, nginx proxies /api to the backend, browser round-trip (status
chip + predict) works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
@albertotb albertotb changed the title Modernize template: Vite, React 19, TypeScript, MUI v9 + supply-chain hardening Template v2: fullstack frontend/ + backend/ (Vite/React 19/TS + rendered FastAPI backend) Aug 8, 2026
claude added 2 commits August 8, 2026 22:02
The Backend/Contract workflows run uvicorn directly, so they cannot
catch images that build but fail to start (exactly the two upstream
bugs this PR patches). The new Docker workflow builds both images via
docker compose and checks the contract through nginx.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
The readiness poll hit GET / (served statically by nginx immediately)
and then asserted /api/ while uvicorn was still booting, yielding a 502.
Poll /api/ through nginx instead, which waits for both containers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
@albertotb
albertotb merged commit 8ea0444 into main Aug 10, 2026
4 checks passed
@albertotb
albertotb deleted the claude/npm-supply-chain-hardening branch August 10, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants