diff --git a/.github/workflows/contract.yml b/.github/workflows/contract.yml index 014cae4..b6f3664 100644 --- a/.github/workflows/contract.yml +++ b/.github/workflows/contract.yml @@ -29,7 +29,13 @@ jobs: run: | uv sync uv run uvicorn app.api:app --port 7000 & - timeout 30 bash -c 'until curl -sf http://localhost:7000/ > /dev/null; do sleep 1; done' + timeout 30 bash -c 'until curl -sf http://localhost:7000/health > /dev/null; do sleep 1; done' + + - name: "Contract: GET /health returns ok" + run: | + body=$(curl -sf http://localhost:7000/health) + echo "$body" + echo "$body" | jq -e '.status == "ok"' - name: "Contract: GET / returns version info" run: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ff0eb34..d506944 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,8 +33,8 @@ jobs: - name: Build and start the stack run: docker compose up -d --build - - name: Wait for the stack (nginx up and backend responding through it) - run: timeout 60 bash -c 'until curl -sf http://localhost:8080/api/ > /dev/null; do sleep 2; done' + - name: Wait for the stack (nginx up and backend healthy through it) + run: timeout 60 bash -c 'until curl -sf http://localhost:8080/api/health > /dev/null; do sleep 2; done' - name: "Frontend served" run: curl -sf http://localhost:8080/ | grep -q "React Template" diff --git a/README.md b/README.md index a3a8c69..342407f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# react-template +# react-fastapi-template Fullstack template at Komorebi AI: a **React frontend** (`frontend/`) paired with a **FastAPI backend** (`backend/`). Use it as the starting point for technical tests, @@ -63,13 +63,15 @@ contract fails visibly. The frontend's typed endpoint wrappers (`frontend/src/api/backend.ts`) match `backend/app/api.py`: -| Endpoint | Response | Used for | -| ------------------- | -------------------------------- | -------------------- | -| `GET /api/` | `{"app-api": "version ..."}` | status/version chip | -| `POST /api/predict` | `{"input": n}` → `{"output": n}` | mock prediction demo | +| Endpoint | Response | Used for | +| ------------------- | -------------------------------- | -------------------------- | +| `GET /api/` | `{"app-api": "version ..."}` | status/version chip | +| `POST /api/predict` | `{"input": n}` → `{"output": n}` | mock prediction demo | +| `GET /api/health` | `{"status": "ok"}` | container healthchecks, CI | Both proxies (Vite in dev, nginx in prod) strip the `/api` prefix before forwarding to -the backend. +the backend. `/health` is the stable endpoint — keep it when replacing the example +endpoints, since the compose healthcheck and CI smoke tests rely on it. ## Starting a project from this template diff --git a/backend/app/api.py b/backend/app/api.py index 2bb70a3..93994b7 100644 --- a/backend/app/api.py +++ b/backend/app/api.py @@ -40,6 +40,16 @@ def read_root() -> dict[str, str]: return {"app-api": f"version {__version__}"} +@app.get("/health") +def health() -> dict[str, str]: + """Health check for container orchestration and monitoring. + + Stable endpoint: keep it when replacing the example endpoints below, + since Docker healthchecks and CI smoke tests rely on it. + """ + return {"status": "ok"} + + @app.post("/predict") def predict(request: Request) -> Response: """Mock prediction endpoint.""" diff --git a/compose.yaml b/compose.yaml index e61744e..defe62f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,6 +3,19 @@ services: build: ./backend # The backend image runs uvicorn on port 80 (see backend/Dockerfile). # Not published to the host: the frontend's nginx proxies /api to it. + healthcheck: + # python (always present in the image) instead of curl/wget (not installed) + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:80/health')", + ] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s frontend: build: ./frontend @@ -12,4 +25,5 @@ services: # Same nginx config as the image default, plus the /api -> backend proxy. - ./frontend/docker/nginx.compose.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - - backend + backend: + condition: service_healthy diff --git a/docs/backend-sync.md b/docs/backend-sync.md index 81385de..ac973ce 100644 --- a/docs/backend-sync.md +++ b/docs/backend-sync.md @@ -19,7 +19,7 @@ three post-render adjustments: backend's pyproject is not at the git root, so the version must be derived from this repo's metadata. (Cleaner long-term fix: an `scm_root` question in the template.) 3. **Point `_src_path`** in `.copier-answers.yml` at the template's GitHub URL and keep - the file — projects created from react-template use it to run `copier update` on + the file — projects created from react-fastapi-template use it to run `copier update` on their own backends. Then `uv lock` (with `SETUPTOOLS_SCM_PRETEND_VERSION`) and `uvx sync-with-uv`, exactly @@ -56,11 +56,11 @@ red PR instead of a silent break. ## Workflow for python-copier-template -Ready to drop in as `.github/workflows/sync-react-template.yml` (mirrors the existing +Ready to drop in as `.github/workflows/sync-react-fastapi-template.yml` (mirrors the existing `sync-template.yml`; requires the same `GH_TOKEN` secret): ```yaml -name: sync-react-template +name: sync-react-fastapi-template on: push: @@ -88,9 +88,9 @@ jobs: run: | copier copy --defaults --vcs-ref=HEAD \ --data project_name="React Template Backend" \ - --data project_description="FastAPI backend for react-template" \ + --data project_description="FastAPI backend for react-fastapi-template" \ --data package_name="app" \ - --data github_repo="react-template" \ + --data github_repo="react-fastapi-template" \ --data project_type="application" \ --data python_version="3.14" \ --data include_api=true \ @@ -98,10 +98,15 @@ jobs: --data include_docker=true \ . /tmp/rendered + # backend/ is a subdirectory of react-fastapi-template, not a repo root: its + # workflows are owned by react-fastapi-template (rendered ones assume repo root) + # and its version must derive from react-fastapi-template's git metadata. + # Keep .copier-answers.yml (pointed at this repo's URL) so projects + # created from react-fastapi-template can `copier update` their backends. - name: Adjust render for subdirectory embedding run: | rm -rf /tmp/rendered/.github - sed -i 's|^\[tool.setuptools_scm\]$|[tool.setuptools_scm]\nroot = ".."|' \ + sed -i 's|^\[tool.setuptools_scm\]$|[tool.setuptools_scm]\n# Backend lives in a subdirectory of the react-fastapi-template repo; version comes\n# from the repo root git metadata (applied by the template sync job)\nroot = ".."|' \ /tmp/rendered/pyproject.toml sed -i 's|^_src_path:.*|_src_path: https://github.com/Komorebi-AI/python-copier-template.git|' \ /tmp/rendered/.copier-answers.yml @@ -116,22 +121,22 @@ jobs: run: uvx sync-with-uv working-directory: /tmp/rendered - - name: Checkout react-template + - name: Checkout react-fastapi-template uses: actions/checkout@v6 with: - repository: Komorebi-AI/react-template + repository: Komorebi-AI/react-fastapi-template token: ${{ secrets.GH_TOKEN }} - path: react-template + path: react-fastapi-template - name: Replace backend/ run: | - rm -rf react-template/backend - mkdir react-template/backend - cp -r /tmp/rendered/. react-template/backend/ + rm -rf react-fastapi-template/backend + mkdir react-fastapi-template/backend + cp -r /tmp/rendered/. react-fastapi-template/backend/ - name: Check for changes id: changes - working-directory: react-template + working-directory: react-fastapi-template run: | git add -A if git diff --cached --quiet; then @@ -142,7 +147,7 @@ jobs: - name: Create pull request if: steps.changes.outputs.has_changes == 'true' - working-directory: react-template + working-directory: react-fastapi-template env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | @@ -158,10 +163,10 @@ jobs: if ! gh pr list --head "$BRANCH" --json number --jq '.[0].number' | grep -q .; then gh pr create \ --title "sync: update backend/ from python-copier-template" \ - --body "Automated sync of backend/ from python-copier-template." + --body "Automated sync of backend/ from [python-copier-template](https://github.com/Komorebi-AI/python-copier-template)." fi - # Requires react-template branch protection with the Backend and + # Requires react-fastapi-template branch protection with the Backend and # Contract checks required, and "Allow auto-merge" enabled. gh pr merge "$BRANCH" --auto --squash ``` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e374791..521ca33 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "react-template", + "name": "react-fastapi-template", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-template", + "name": "react-fastapi-template", "version": "1.0.0", "dependencies": { "@emotion/react": "^11.14.0", diff --git a/frontend/package.json b/frontend/package.json index e1d9e2e..a1fcf0f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "react-template", + "name": "react-fastapi-template", "version": "1.0.0", "private": true, "type": "module",