# test-app-stack A minimal full-stack reference project: - **Backend** — [ElysiaJS](https://elysiajs.com) running on [Bun](https://bun.sh). Exposes a small JSON API. - **Frontend** — [TanStack Start](https://tanstack.com/start) (React 19, SSR via Vite) that calls the backend and renders the result. - **Orchestration** — `docker-compose.yml` wiring both services behind an existing [Traefik](https://traefik.io) reverse proxy with automatic Let's Encrypt TLS. ``` test-app-stack/ ├── backend/ # ElysiaJS (Bun) │ ├── src/index.ts # API: / /api/health /api/hello │ ├── package.json │ ├── tsconfig.json │ └── Dockerfile ├── frontend/ # TanStack Start (React, SSR) │ ├── src/ │ │ ├── router.tsx │ │ └── routes/ │ │ ├── __root.tsx # HTML shell │ │ └── index.tsx # Home page — fetches /api/hello │ ├── vite.config.ts │ ├── package.json │ ├── tsconfig.json │ └── Dockerfile ├── docker-compose.yml # both services + Traefik labels ├── .env.example └── README.md ``` --- ## API surface (backend) | Method | Path | Description | | ------ | -------------- | --------------------------------------------- | | GET | `/` | Service banner / metadata | | GET | `/api/health` | Health probe (`{ status: "healthy", ... }`) | | GET | `/api/hello` | Demo payload consumed by the frontend | The frontend's home page (`frontend/src/routes/index.tsx`) calls `GET /api/hello` against the URL in `VITE_API_URL` and shows the response — this is the front ↔ back connection. --- ## Local development Two terminals. **Backend** (requires [Bun](https://bun.sh)): ```bash cd backend bun install bun run dev # http://localhost:3000 ``` **Frontend** (requires Node 22+): ```bash cd frontend npm install # point the UI at the local backend echo 'VITE_API_URL=http://localhost:3000' > .env npm run dev # http://localhost:3000 (use a different port if backend is on 3000) ``` > Both default to port 3000. For local dev run the backend on another port, e.g. > `PORT=3001 bun run dev`, and set `VITE_API_URL=http://localhost:3001`. --- ## Manual build & deploy (Docker + Traefik) This stack does **not** ship its own Traefik. It attaches to the Traefik instance and the external `web` Docker network already running on the VPS (from the forgejo stack), and reuses its `le` cert resolver. ### 1. Prerequisites - Docker + Docker Compose on the host. - An existing Traefik container attached to an external network named `web`, with a cert resolver named `le` and a `websecure` (:443) entrypoint. - **DNS**: create `A` records for both hostnames pointing at the VPS IP: - `test-app.emmanuelariasa.com` → frontend - `test-app-api.emmanuelariasa.com` → backend (Adjust the names via `.env`. Without DNS, Traefik can't issue certificates.) ### 2. Configure ```bash cp .env.example .env # edit .env if you want different hostnames / CORS origin ``` ### 3. Build the images ```bash docker compose build ``` `VITE_API_URL` is passed as a build arg to the frontend because Vite inlines it into the client bundle at build time — rebuild the frontend if the API hostname changes. ### 4. Start ```bash docker compose up -d docker compose ps docker compose logs -f ``` Traefik picks up the containers via their labels and provisions TLS automatically. Once certificates are issued: - Frontend → https://test-app.emmanuelariasa.com - Backend → https://test-app-api.emmanuelariasa.com/api/health ### 5. Update / redeploy ```bash git pull docker compose build docker compose up -d # recreates only changed services ``` ### 6. Tear down ```bash docker compose down # add -v to also drop the app-internal network/volumes ``` --- ## How the pieces connect ``` Internet ──TLS──▶ Traefik (:443, existing) │ Host(test-app.emmanuelariasa.com) → frontend:3000 │ Host(test-app-api.emmanuelariasa.com) → backend:3000 ▼ web (external docker network) ├── frontend (TanStack Start SSR) └── backend (ElysiaJS/Bun) ── app-internal ──▶ (future DB) ``` The browser loads the SSR'd frontend, then calls the backend directly over its public HTTPS hostname (`VITE_API_URL`). CORS on the backend is restricted to `FRONTEND_ORIGIN`. --- ## Notes - `frontend/src/routeTree.gen.ts` is generated on first `vite dev`/`build` and is git-ignored. - Bun lockfile (`bun.lock`) and npm lockfile (`package-lock.json`) are created on first install; commit them for reproducible builds. - This repository is intentionally deploy-ready but **not auto-deployed** — run the steps above manually.