dev: fix the Vite proxy and add a one-command dev runner

Running `pnpm dev` alone gave ECONNREFUSED on every request, because nothing
was listening on :7788. Two separate causes:

- the backend simply was not running, and
- Vite bound [::1]:5173 while the proxy target was "localhost", which Node
  resolves to ::1 first. The Go server binds IPv4, so even with the backend up
  the proxy could fail before falling back.

- vite.config.ts: proxy target is now 127.0.0.1 explicitly and overridable via
  VELLUM_API; server.host is true so both stacks are served.
- scripts/dev.sh + `make dev`: builds the backend, waits for /healthz, starts
  Vite, and stops the backend on exit. Ctrl-C tears down both.
- README documents that running Vite alone needs the backend already up.
This commit is contained in:
mithral 2026-10-10 16:32:32 -04:00
parent 1d7831ac10
commit 7754b0f504
4 changed files with 85 additions and 4 deletions

View file

@ -7,7 +7,11 @@ BINARY := vellum
VERSION ?= 0.4.0-dev VERSION ?= 0.4.0-dev
LDFLAGS := -s -w -X main.version=$(VERSION) LDFLAGS := -s -w -X main.version=$(VERSION)
.PHONY: build web run test fmt vet tidy check docker clean .PHONY: build web build-go dev run test fmt vet tidy check docker clean
# Backend + Vite dev server together, with hot reload. Ctrl-C stops both.
dev:
./scripts/dev.sh
# Build the frontend first: the Go build embeds web/dist. # Build the frontend first: the Go build embeds web/dist.
build: web build: web

View file

@ -141,9 +141,18 @@ The application never writes to `/media`.
## Development ## Development
```bash ```bash
make dev # backend + Vite dev server together, with hot reload
make build # builds the frontend, then the Go binary with it embedded make build # builds the frontend, then the Go binary with it embedded
make build-go # Go only, when the frontend is unchanged make build-go # Go only, when the frontend is unchanged
make check # gofmt + vet + test make check # gofmt + vet + test
make run # run the server on :7788 make run # run the server on :7788
cd web && pnpm dev # frontend dev server, proxies /api to :7788 cd web && pnpm dev # frontend only; needs the backend running on :7788
``` ```
`make dev` is the one to reach for while developing: it builds and starts the
backend, waits for it to answer, then starts Vite. Vite proxies `/api` to the
backend so the browser sees a single origin and sessions behave exactly as in
production. Ctrl-C stops both.
If you run Vite on its own, the backend must already be running, or every
request fails with `ECONNREFUSED` on `:7788`.

61
scripts/dev.sh Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Run the backend and the Vite dev server together.
#
# ./scripts/dev.sh
#
# The Go server runs in the background; the Vite dev server runs in the
# foreground. Ctrl-C stops both. Vite proxies /api to the Go server, so the
# browser sees one origin and sessions behave exactly as in production.
set -euo pipefail
cd "$(dirname "$0")/.."
# A local preview instance, so this works on a fresh clone without touching
# your real media or your real database.
export VELLUM_DATA_DIR="${VELLUM_DATA_DIR:-$PWD/demo/data}"
export VELLUM_CONFIG_DIR="${VELLUM_CONFIG_DIR:-$PWD/demo/config}"
export VELLUM_LISTEN="${VELLUM_LISTEN:-127.0.0.1:7788}"
export VELLUM_ADMIN_USER="${VELLUM_ADMIN_USER:-admin}"
export VELLUM_ADMIN_PASSWORD="${VELLUM_ADMIN_PASSWORD:-vellumdemo}"
export VELLUM_LOG_LEVEL="${VELLUM_LOG_LEVEL:-info}"
if ! command -v go >/dev/null 2>&1; then
export PATH="$PATH:/usr/lib/golang/bin"
fi
echo "building backend..."
CGO_ENABLED=0 go build -trimpath -o vellum ./cmd/vellum
echo "starting backend on ${VELLUM_LISTEN}"
./vellum &
BACKEND=$!
cleanup() {
echo
echo "stopping backend..."
kill "$BACKEND" 2>/dev/null || true
wait "$BACKEND" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
# Wait for the API to answer before handing over to Vite, so the first page
# load does not race the backend's startup.
for _ in $(seq 1 50); do
if curl -fsS -m 1 "http://${VELLUM_LISTEN#*:}/healthz" >/dev/null 2>&1; then
break
fi
if ! kill -0 "$BACKEND" 2>/dev/null; then
echo "backend exited during startup" >&2
exit 1
fi
sleep 0.1
done
echo
echo "backend: http://${VELLUM_LISTEN} (${VELLUM_ADMIN_USER} / ${VELLUM_ADMIN_PASSWORD})"
echo "frontend: http://localhost:5173"
echo
cd web
pnpm install --silent
pnpm dev

View file

@ -1,6 +1,11 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte' import { svelte } from '@sveltejs/vite-plugin-svelte'
// The backend the dev server proxies to. 127.0.0.1 rather than localhost on
// purpose: Node resolves "localhost" to ::1 first, and the Go server binds
// IPv4, so the proxy would fail before falling back.
const apiTarget = process.env.VELLUM_API ?? 'http://127.0.0.1:7788'
// The bundle is embedded into the Go binary and served from /, so all asset // The bundle is embedded into the Go binary and served from /, so all asset
// paths must be relative (base: './'). // paths must be relative (base: './').
export default defineConfig({ export default defineConfig({
@ -13,9 +18,11 @@ export default defineConfig({
}, },
server: { server: {
port: 5173, port: 5173,
// Listen on both stacks so a browser using either localhost works.
host: true,
proxy: { proxy: {
'/api': 'http://localhost:7788', '/api': apiTarget,
'/healthz': 'http://localhost:7788', '/healthz': apiTarget,
}, },
}, },
}) })