# How the WebUI Admin Console Interacts with the Go Backend in DS2API

> Discover how the DS2API WebUI admin console communicates with its Go backend through REST endpoints. Learn about Vite proxy and static file serving for seamless interaction.

- Repository: [CJACK./ds2api](https://github.com/CJackHwang/ds2api)
- Tags: how-to-guide
- Published: 2026-04-26

---

**The DS2API WebUI admin console communicates with the Go backend via REST endpoints under the `/admin` prefix, using a Vite development proxy in local development and static file serving with API route registration in production.**

The DS2API repository provides a modern React-based administrative interface that seamlessly integrates with its Go server infrastructure. This architecture separates the frontend presentation layer from backend business logic through a clearly defined HTTP JSON interface. Understanding how the WebUI admin console interacts with the Go backend requires examining both the development proxy configuration and the production static file serving mechanism.

## Architecture Overview

The interaction follows a layered pattern with distinct responsibilities. The **frontend** handles UI components and state management through a thin `apiFetch` wrapper located in files like [`webui/src/features/settings/settingsApi.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/src/features/settings/settingsApi.js). During development, a **Vite proxy** forwards requests from port 5173 to the Go server on port 5001. In production, the **Go server** serves pre-built static files from `static/admin` while simultaneously registering the same API routes. The **backend handlers** in `internal/httpapi/admin/*` implement the actual business logic for configuration, accounts, and proxy management.

## Development Request Flow

During development, the Vite dev server runs on `http://localhost:5173` while the Go backend listens on port 5001. When the admin UI makes a request to `/admin/settings`, Vite's proxy configuration intercepts the call and forwards it to the backend.

The proxy configuration in [`webui/vite.config.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/vite.config.js) distinguishes between API calls and page navigation requests:

```javascript
// webui/vite.config.js – proxy definition
proxy: {
  '/admin': {
    target: 'http://localhost:5001',
    changeOrigin: true,
    // page navigation → let Vite serve index.html
    bypass(req) {
      const url = req.url
      if (url === '/admin' || url === '/admin/' || url === '/admin?') {
        console.log('[Vite Proxy] Bypass (page):', url)
        return '/index.html'
      }
      console.log('[Vite Proxy] Proxy to backend:', url)
    },
  },
  // … other proxies (e.g., /v1 for the public API)
},

```

This configuration ensures that API requests reach the Go server while the single-page application (SPA) routes are handled by Vite's dev server. The Go server receives the request through the router mounted in [`internal/server/router.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/server/router.go).

## Production Static Serving

In production, the React application is built using `npm run build`, which outputs static assets to the `static/admin` directory. The Go binary embeds a handler in [`internal/webui/handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/webui/handler.go) that serves these files directly and implements SPA fallback behavior.

```go
// internal/webui/handler.go – serving built SPA
func (h *Handler) admin(w http.ResponseWriter, r *http.Request) {
    staticDir := resolveStaticAdminDir(h.StaticDir)
    if fi, err := os.Stat(staticDir); err == nil && fi.IsDir() {
        h.serveFromDisk(w, r, staticDir) // serve JS/CSS or index.html
        return
    }
    http.Error(w, "WebUI not built. Run `cd webui && npm run build` first.", http.StatusNotFound)
}

```

If the request path does not resolve to a static asset (such as `/admin/settings`), the handler falls back to serving [`index.html`](https://github.com/CJackHwang/ds2api/blob/main/index.html) only for page routes (`/admin` or `/admin/`). API calls continue to match registered routes handled by the dedicated admin handlers.

## Backend Route Registration

The Go server registers all admin endpoints in [`internal/server/router.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/server/router.go) using Chi router middleware. This registration occurs regardless of environment, ensuring consistent API behavior.

```go
// internal/server/router.go – mounting admin routes
r.Route("/admin", func(ar chi.Router) {
    admin.RegisterRoutes(ar, adminHandler) // all admin handlers
})

```

The router also includes a fallback mechanism for unknown admin paths that delegates to the WebUI handler for SPA routing:

```go
r.NotFound(func(w http.ResponseWriter, req *http.Request) {
    if strings.HasPrefix(req.URL.Path, "/admin/") && webuiHandler.HandleAdminFallback(w, req) {
        return
    }
    http.NotFound(w, req)
})

```

## Frontend API Communication

All React components interact with the backend through a centralized `apiFetch` utility that automatically prefixes `/admin` to requests and handles JSON serialization. The [`webui/src/features/settings/settingsApi.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/src/features/settings/settingsApi.js) file demonstrates this pattern for CRUD operations:

```javascript
// webui/src/features/settings/settingsApi.js
export async function fetchSettings(apiFetch, t) {
  const res = await apiFetch('/admin/settings')
  return { res, data: await res.json() }
}
export async function putSettings(apiFetch, payload) {
  const res = await apiFetch('/admin/settings', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
  return { res, data: await res.json() }
}

```

Components consume these helpers through React hooks, ensuring the UI never directly accesses Go structs but instead relies on the HTTP JSON contract. Authentication logic follows the same pattern, with [`webui/src/app/useAdminAuth.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/src/app/useAdminAuth.js) handling JWT tokens via `/admin/login` and `/admin/verify` endpoints.

## Summary

- **Development proxy**: Vite forwards `/admin/*` requests from port 5173 to the Go backend on port 5001, while serving the SPA directly
- **Production serving**: The Go binary serves built static files from `static/admin` with automatic fallbacks to [`index.html`](https://github.com/CJackHwang/ds2api/blob/main/index.html) for client-side routes
- **API routing**: All admin endpoints are registered under `/admin` in [`internal/server/router.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/server/router.go) using Chi router middleware
- **Frontend abstraction**: The React UI uses a shared `apiFetch` wrapper located in [`webui/src/features/settings/settingsApi.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/src/features/settings/settingsApi.js) to communicate through JSON HTTP contracts
- **Source locations**: Business logic resides in `internal/httpapi/admin/*`, static serving in [`internal/webui/handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/webui/handler.go), and proxy configuration in [`webui/vite.config.js`](https://github.com/CJackHwang/ds2api/blob/main/webui/vite.config.js)

## Frequently Asked Questions

### What port does the DS2API Go backend use during development?

The Go backend listens on **port 5001** during development. The Vite dev server running on port 5173 proxies all `/admin/*` API requests to this port, allowing the React frontend to communicate with the backend as if they were the same origin.

### How does DS2API handle client-side routing in production?

When a request to `/admin/settings` or similar routes arrives in production, the [`internal/webui/handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/webui/handler.go) first attempts to serve a matching static file. If no file exists, it falls back to serving [`index.html`](https://github.com/CJackHwang/ds2api/blob/main/index.html) for page routes, allowing the React Router to handle the navigation. API routes are intercepted by the registered handlers before this fallback occurs.

### Where are the admin API business logic handlers located?

All admin-specific business logic is implemented in the `internal/httpapi/admin/*` package. This includes handlers for settings, accounts, proxy configuration, and authentication. These handlers are registered in [`internal/server/router.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/server/router.go) under the `/admin` route prefix.

### What happens if the WebUI static files are not built before running the Go server?

If the `static/admin` directory does not exist, the `admin` handler in [`internal/webui/handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/webui/handler.go) returns an HTTP 404 error with the message: *"WebUI not built. Run `cd webui && npm run build` first."* This prevents deployment of a broken admin interface and provides clear instructions for resolving the issue.