Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<title>React Template</title>"
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions backend/app/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
16 changes: 15 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
37 changes: 21 additions & 16 deletions docs/backend-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -88,20 +88,25 @@ 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 \
--data include_cli=false \
--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
Expand All @@ -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
Expand All @@ -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: |
Expand All @@ -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
```
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "react-template",
"name": "react-fastapi-template",
"version": "1.0.0",
"private": true,
"type": "module",
Expand Down