test-app-stack/README.md
mane a346cf3a5c Initial commit: ElysiaJS backend + TanStack Start frontend + Traefik compose
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 04:01:48 +00:00

4.9 KiB

test-app-stack

A minimal full-stack reference project:

  • BackendElysiaJS running on Bun. Exposes a small JSON API.
  • FrontendTanStack Start (React 19, SSR via Vite) that calls the backend and renders the result.
  • Orchestrationdocker-compose.yml wiring both services behind an existing Traefik 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):

cd backend
bun install
bun run dev          # http://localhost:3000

Frontend (requires Node 22+):

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

cp .env.example .env
# edit .env if you want different hostnames / CORS origin

3. Build the images

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

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:

5. Update / redeploy

git pull
docker compose build
docker compose up -d        # recreates only changed services

6. Tear down

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.