Initial commit: ElysiaJS backend + TanStack Start frontend + Traefik compose
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
a346cf3a5c
17 changed files with 505 additions and 0 deletions
160
README.md
Normal file
160
README.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue