Sfoglia il codice sorgente

feat: add API proxy through Next.js for remote access and security

BREAKING CHANGE: Web client now uses Next.js proxy by default

Benefits:
- Remote access works automatically (clients connect to web server)
- More secure: only port 3000 exposed, API port 3001 stays internal
- No CORS issues with single-origin requests
- Simpler deployment configuration

Configuration:
- Remove NEXT_PUBLIC_WATCH_FINISHED_API to use proxy (recommended)
- Set API_URL env var for Next.js server (default: http://localhost:3001)
- Old direct connection available via NEXT_PUBLIC_WATCH_FINISHED_API

Changes:
- Add Next.js rewrites for /api/* and /socket.io/* paths
- Update api.ts to support both proxy and direct connection modes
- Update websocket.ts to use window.location.origin as default
- Update Dockerfile to only expose port 3000
- Update docker-compose.yml ports and add API_URL env var
- Add .env.example documenting configuration options
Timothy Pomeroy 1 mese fa
parent
commit
009dc3b5cb

+ 10 - 0
CHANGELOG.md

@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- **API Proxy Architecture**: Next.js now proxies all API and WebSocket requests to backend service
+  - Eliminates need to expose API port (3001) externally
+  - Enables remote access to web UI without CORS issues
+  - More secure deployment with single public port (3000)
+- `.env.example` file documenting environment variables
 - localStorage persistence for filter selections, search terms, and sort preferences across Files and Tasks pages
 - Proper loading states to prevent "flash of empty content" on fresh page loads
 - Real-time progress tracking with visual progress bar and color-coded states
@@ -17,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+- **BREAKING**: Web client now uses API proxy by default instead of direct API connection
+  - Remove `NEXT_PUBLIC_WATCH_FINISHED_API` from `.env.local` to use proxy (recommended)
+  - Set `API_URL` for Next.js server to locate backend (default: http://localhost:3001)
+  - Old behavior available by setting `NEXT_PUBLIC_WATCH_FINISHED_API` explicitly
+- Docker configuration now only exposes port 3000 (web), port 3001 (API) is internal
 - **BREAKING**: Simplified `files` table schema by removing `status` column
   - Files table now only tracks observations: `dataset`, `input`, `output`, `date`
   - Task processing states managed exclusively in `tasks` table

+ 4 - 4
Dockerfile

@@ -30,8 +30,8 @@ FROM dependencies AS development
 # Copy all source code
 COPY . .
 
-# Expose ports
-EXPOSE 3000 3001 3002
+# Expose ports (only web port 3000 is needed externally, API is proxied)
+EXPOSE 3000
 
 # Set the CLI as the default entrypoint for development
 ENTRYPOINT ["pnpm", "run", "cli"]
@@ -66,8 +66,8 @@ COPY --from=builder /app/apps/cli/dist ./apps/cli/dist
 # Copy necessary config files for Next.js
 COPY apps/web/next.config.ts apps/web/next.config.js ./apps/web/
 
-# Expose ports
-EXPOSE 3000 3001 3002
+# Expose ports (only web port 3000 is needed externally, API is proxied)
+EXPOSE 3000
 
 # Default command runs all services in production
 CMD ["pnpm", "start"]

+ 9 - 0
apps/web/.env.example

@@ -0,0 +1,9 @@
+# Backend API URL for Next.js server-side proxy
+# This is used by the Next.js server to proxy requests to the NestJS API
+# Default: http://localhost:3001
+API_URL=http://localhost:3001
+
+# Optional: Direct API connection (bypasses proxy)
+# Only set this if you need direct client-to-API communication
+# Leave unset to use the built-in proxy (recommended for production)
+# NEXT_PUBLIC_WATCH_FINISHED_API=http://localhost:3001

+ 15 - 0
apps/web/next.config.ts

@@ -2,6 +2,21 @@ import type { NextConfig } from "next";
 
 const nextConfig: NextConfig = {
   /* config options here */
+  async rewrites() {
+    const apiUrl = process.env.API_URL || "http://localhost:3001";
+
+    return [
+      {
+        source: "/api/:path*",
+        destination: `${apiUrl}/:path*`
+      },
+      // WebSocket support for socket.io
+      {
+        source: "/socket.io/:path*",
+        destination: `${apiUrl}/socket.io/:path*`
+      }
+    ];
+  }
 };
 
 export default nextConfig;

+ 9 - 1
apps/web/src/lib/api.ts

@@ -1,7 +1,15 @@
+// API_BASE now uses relative path for proxy, or falls back to direct API URL
 export const API_BASE = process.env.NEXT_PUBLIC_WATCH_FINISHED_API || "/api";
 
 function buildUrl(path: string, params?: any) {
-  const url = new URL(path, API_BASE);
+  // If API_BASE starts with http, use it as absolute URL
+  // Otherwise, use it as relative path (for proxy)
+  const base = API_BASE.startsWith("http") ? API_BASE : "";
+  const fullPath = API_BASE.startsWith("http") ? path : `${API_BASE}${path}`;
+
+  const url = base
+    ? new URL(fullPath, base)
+    : new URL(fullPath, window.location.origin);
   if (params && typeof params === "object") {
     Object.entries(params).forEach(([k, v]) => {
       if (v !== undefined && v !== null) url.searchParams.append(k, String(v));

+ 3 - 4
apps/web/src/lib/websocket.ts

@@ -6,18 +6,17 @@ class WebSocketService {
   private maxReconnectAttempts = 5;
 
   connect(url?: string) {
+    // Use relative path for proxy support, or explicit URL if provided
     const defaultUrl =
-      process.env.NEXT_PUBLIC_WATCH_FINISHED_API || "http://localhost:3001";
+      process.env.NEXT_PUBLIC_WATCH_FINISHED_API || window.location.origin;
     const wsUrl = url || defaultUrl;
 
-    if (this.socket?.connected) {
-      return this.socket;
-    }
     if (this.socket?.connected) {
       return this.socket;
     }
 
     this.socket = io(wsUrl, {
+      path: "/socket.io",
       transports: ["websocket", "polling"]
     });
 

+ 4 - 4
docker-compose.yml

@@ -7,14 +7,14 @@ services:
       context: .
       target: development
     ports:
-      - "3000:3000" # Web app
-      - "3001:3001" # Service app
+      - "3000:3000" # Web app (API proxied through Next.js)
     volumes:
       - .:/app
       - /app/node_modules
       - ./data:/app/data # Persist database files
     environment:
       - NODE_ENV=development
+      - API_URL=http://localhost:3001 # Internal API URL for proxy
     # CLI is now the default entrypoint, can be overridden with command
     # command: pnpm dev  # Uncomment to run dev servers instead of CLI
     stdin_open: true # Keep stdin open for interactive CLI
@@ -26,12 +26,12 @@ services:
       context: .
       target: production
     ports:
-      - "3000:3000" # Web app
-      - "3001:3001" # Service app
+      - "3000:3000" # Web app (API proxied through Next.js)
     volumes:
       - ./data:/app/data # Persist database files only
     environment:
       - NODE_ENV=production
+      - API_URL=http://localhost:3001 # Internal API URL for proxy
     # Runs all services (web + service) in production by default
     # Override with command to run CLI: ["node", "apps/cli/dist/index.js"]
     stdin_open: true # Keep stdin open for CLI if used