From 04c845cd82653189a4919911e8458f9fc2bf77cb Mon Sep 17 00:00:00 2001 From: Young850 Date: Sun, 18 Oct 2026 13:16:38 +0100 Subject: [PATCH] Closes #864 - Add API_PREFIX environment variable support for reverse proxy deployments - When API_PREFIX is set, all routes are nested under that prefix - Example: API_PREFIX=/api/v1 makes /invoices available at /api/v1/invoices - Properly handles path prefix with leading slash and trims trailing slash - Maintains backward compatibility - no prefix when API_PREFIX is empty Implementation: - Split router into api_routes and conditionally nest under API_PREFIX - Supports clean deployment scenarios where API is behind a reverse proxy with path rewrite --- backend/src/lib.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/src/lib.rs b/backend/src/lib.rs index df14787..b570b25 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -65,13 +65,17 @@ impl AppState { /// This is the single source of truth for the route table: `main` serves it /// in production and integration tests exercise it directly, so the two can /// never drift apart. +/// +/// Supports API_PREFIX environment variable for deployments behind reverse proxies +/// with path prefixes (e.g., API_PREFIX=/api/v1 routes /api/v1/invoices instead of /invoices). pub fn build_router(state: AppState) -> Router { // Rate-limiter layer: config is read from RATE_LIMIT_POINTS / // RATE_LIMIT_DURATION (defaults: 60 requests per 60-second window, per IP). let rl_config = RateLimitConfig::from_env(); let rl_layer = RateLimiterLayer::new(new_store(), rl_config); - Router::new() + // Build the main API routes + let api_routes = Router::new() .route("/health/rpc", axum::routing::get(get_rpc_health)) .route("/health/ready", axum::routing::get(readiness_probe)) .route("/invoices", axum::routing::post(create_invoice)) @@ -80,7 +84,17 @@ pub fn build_router(state: AppState) -> Router { .route("/invoices/:id/cancel", axum::routing::post(cancel_invoice)) .route("/invoices/:id/refund", axum::routing::post(refund_invoice)) .layer(rl_layer) - .with_state(state) + .with_state(state.clone()); + + // Support optional API_PREFIX for reverse proxy deployments with path prefixes + // If API_PREFIX is set, nest all routes under that prefix + let api_prefix = std::env::var("API_PREFIX").unwrap_or_default(); + if api_prefix.is_empty() { + api_routes + } else { + let prefix = api_prefix.trim_end_matches('/'); + Router::new().nest(prefix, api_routes) + } } #[derive(utoipa::OpenApi)]