diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5c9437..e139a1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,6 @@ jobs: file: backend/Dockerfile push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} build-args: | - VITE_API_BASE_URL=/api GIT_VERSION=${{ steps.git-version.outputs.value }} tags: ${{ steps.backend-meta.outputs.tags }} labels: ${{ steps.backend-meta.outputs.labels }} diff --git a/README.md b/README.md index 70f4eb9..360d4aa 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,16 @@ Minimal portfolio app. ## Frontend backend URL -The frontend reads the backend base URL from `VITE_API_BASE_URL`. +The frontend always calls the backend under the relative `/api` path. -1. Copy [`web/.env.example`](web/.env.example) to `web/.env.local`. -2. Set `VITE_API_BASE_URL` to the backend URL you want the frontend to use. -3. Start the frontend with `npm run dev` from `web/`. - -Example: - -```bash -cp web/.env.example web/.env.local -echo 'VITE_API_BASE_URL=http://127.0.0.1:3000/api' > web/.env.local -``` - -For local Vite development, set `VITE_API_BASE_URL` explicitly. If it is not set, the frontend defaults to `/api`, which is the path the backend exposes its API under when it serves the bundled frontend. +- Production: the backend serves both the API and the bundled frontend, so same-origin `/api` requests just work. +- Local development: the Vite dev server proxies `/api` to `http://127.0.0.1:3000` (see [`web/vite.config.ts`](web/vite.config.ts)), so no environment variable is needed. Start the backend with `cargo run` from `backend/`, then `npm run dev` from `web/`. ## Deployment The backend serves both the API and the bundled Vite frontend from a single container image: -- [`backend/Dockerfile`](backend/Dockerfile) builds the Rust API and bundles the static frontend (built with `VITE_API_BASE_URL=/api`) into `/app/web` +- [`backend/Dockerfile`](backend/Dockerfile) builds the Rust API and bundles the static frontend (which targets `/api`) into `/app/web` - [`docker-compose.yml`](docker-compose.yml) deploys the prebuilt tagged image - [`docker-compose.build.yml`](docker-compose.build.yml) adds local build support on top of the base compose file @@ -69,7 +59,7 @@ export APP_TAG=dev docker compose -f docker-compose.yml -f docker-compose.build.yml up --build ``` -The base compose file keeps the final image name and tag stable. The build override adds `build:` and `pull_policy: never` so `--build` does not try to pull first. The build sets `VITE_API_BASE_URL=/api` by default. +The base compose file keeps the final image name and tag stable. The build override adds `build:` and `pull_policy: never` so `--build` does not try to pull first. ### Runtime endpoints @@ -81,7 +71,7 @@ The compose file uses a named volume for backend SQLite data. ### Frontend URL behavior -The bundled frontend always targets `VITE_API_BASE_URL` as set at build time, defaulting to `/api`. For a deployment where the API is hosted on a separate public origin, rebuild the image with the desired public `VITE_API_BASE_URL`. +The bundled frontend always targets the relative `/api` path on its own origin. To host the API on a separate public origin, place a reverse proxy that forwards `/api` to the backend. ### CI diff --git a/backend/Dockerfile b/backend/Dockerfile index ca1e5f9..21f9fa1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,8 +6,6 @@ WORKDIR /web COPY web/package.json web/package-lock.json ./ RUN npm ci COPY web/ ./ -ARG VITE_API_BASE_URL=/api -ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} ARG GIT_VERSION ENV GIT_VERSION=${GIT_VERSION} RUN npm run build diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 7a4edba..50492b5 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -3,6 +3,4 @@ services: build: context: . dockerfile: backend/Dockerfile - args: - VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api} pull_policy: never diff --git a/web/.env.example b/web/.env.example deleted file mode 100644 index 75be0ce..0000000 --- a/web/.env.example +++ /dev/null @@ -1 +0,0 @@ -VITE_API_BASE_URL=http://127.0.0.1:3000/api diff --git a/web/README.md b/web/README.md index 0e5f9f0..c0a0e9b 100644 --- a/web/README.md +++ b/web/README.md @@ -1,20 +1,8 @@ # siniscalco web -## Set the backend URL +## Backend URL -The frontend uses `VITE_API_BASE_URL` as the backend base URL. +The frontend calls the backend under the relative `/api` path. -For local Vite development, point it directly at the backend: - -1. Copy `.env.example` to `.env.local`. -2. Set `VITE_API_BASE_URL`. -3. Run `npm run dev`. - -Example: - -```bash -cp .env.example .env.local -echo 'VITE_API_BASE_URL=http://127.0.0.1:3000/api' > .env.local -``` - -If `VITE_API_BASE_URL` is not set, the app defaults to `/api`. That default is intended for the container build, where the backend serves the bundled frontend and exposes its API under `/api`. +- Local development: the Vite dev server proxies `/api` to `http://127.0.0.1:3000` (see `vite.config.ts`). Start the backend with `cargo run` from `backend/`, then run `npm run dev`. +- Production: the backend serves the bundled frontend and exposes its API under `/api`, so same-origin requests work out of the box. diff --git a/web/src/lib/env.ts b/web/src/lib/env.ts index 93ff344..1a4815a 100644 --- a/web/src/lib/env.ts +++ b/web/src/lib/env.ts @@ -1,5 +1,7 @@ +export const API_BASE_URL = "/api"; + export function getApiBaseUrl() { - return import.meta.env.VITE_API_BASE_URL?.trim() || "/api"; + return API_BASE_URL; } export function getHealthApiUrl() { diff --git a/web/vite.config.ts b/web/vite.config.ts index 0341ba9..6ccef5a 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -22,6 +22,12 @@ function getGitVersion(): string { // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()], + server: { + proxy: { + // Local development only: forward API calls to the backend started with `cargo run`. + "/api": "http://127.0.0.1:3000", + }, + }, define: { __APP_VERSION__: JSON.stringify(getGitVersion()), },