Skip to content

API URL Handling

API URL Handling

Overview

This page documents the current trailing-slash and proxy behavior used by the API, frontend, and nginx layers.

The important pieces are:

  • the API runs with redirect_slashes=False
  • the frontend request functions in packages/tracker-shared/src/api/*.ts hardcode the correct trailing-slash form per endpoint (shared by both tracker-frontend-svelte and tracker-admin-svelte)
  • nginx preserves the request path and forwards redirects where needed

Current Behavior

With redirect_slashes=False, the API accepts the path as written instead of issuing redirect responses for slash mismatches.

That means the client and proxy layers need to stay consistent about:

  • endpoints that use a trailing slash
  • endpoints that intentionally do not use one, such as auth routes
  • forwarded headers when the app is behind HTTPS

Frontend

There is no central URL-rewriting adapter in the frontend today. The shared request() helper in packages/tracker-shared/src/api/client.ts just appends whatever path it's given to API_BASE_URL — it does not add, strip, or otherwise inspect trailing slashes.

Instead, each API module under packages/tracker-shared/src/api/ hardcodes the correct path literally, matching the backend contract at the call site. For example, in trackers.ts, the list endpoint is called as /trackers/ (trailing slash, matching a collection route) while a single-tracker lookup is called as `/trackers/${id}` (no trailing slash). Auth endpoints in auth.ts (e.g. /auth/login/json, /users/me) likewise never carry a trailing slash. Both tracker-frontend-svelte and tracker-admin-svelte share these same request functions, so they stay consistent by construction rather than through runtime normalization logic.

Static file URLs (e.g. /static/images/...) aren't touched by the API client at all — they're rendered directly as <img> src values and never passed through request().

nginx

Both tracker-frontend-svelte/docker/nginx.conf and tracker-admin-svelte/docker/nginx.conf proxy the API through a generic /api/ location, plus a /static/ location for served files (images, etc.):

location /static/ {
    proxy_pass $api_upstream;
    ...
}

location /api/ {
    proxy_pass $api_upstream;
    proxy_redirect off;
    proxy_intercept_errors on;
    error_page 301 302 307 = @handle_redirect;
}

location @handle_redirect {
    set $saved_redirect_location $upstream_http_location;
    proxy_pass $saved_redirect_location;
}

The admin app's nginx config used to also carry ~10 exact-match location = blocks for specific endpoints (e.g. /api/v1/auth/login/json, /api/v1/users/me), inherited from the old React admin app. These were confirmed redundant and removed during the Svelte cutover — the generic /api/ block already forwards headers (Authorization, Cookie) and handles redirects identically. See docs/development/svelte-staging-cutover-todo.md for how that was verified. tracker-frontend-svelte's nginx config never had the extra blocks and needed no changes.

HTTPS

When the app runs behind a load balancer or HTTPS proxy, also keep these headers correct:

  • Host
  • X-Forwarded-For
  • X-Forwarded-Proto

See HTTPS Behind a Load Balancer for the HTTPS-specific side of the setup.

Troubleshooting

  • If the browser sees mixed-content errors, check X-Forwarded-Proto and redirect behavior.
  • If the API returns unexpected redirects, check the trailing-slash rule for that endpoint.
  • If nginx cannot reach the upstream, check the Host header and any upstream DNS issues.