Proxy (BFF)

HTTP + WebSocket reverse proxy

The Proxy is a backend-for-frontend you run alongside your web app. It forwards both HTTP and WebSocket traffic to your services, rewrites paths, and — most importantly — injects auth tokens from httpOnly cookies so the browser never holds a credential.

It solves the awkward parts of a split frontend/IDP deployment: cross-origin cookies, WebSocket auth (which can't send custom headers from the browser), and silent token refresh on 401. You describe targets; the proxy handles cookie parsing, JWT extraction, path matching and reconnection.

Spin up both protocols at once with startWsProxyServer / a ProxyServerConfig, or mount the HTTP and WS handlers individually inside an existing server.

The proxy server#

ProxyServerConfig binds an HTTP proxy and a WS proxy under one port. createHttpProxyHandler and createWsProxyHandler expose the same logic as standalone handlers when you want to embed them.

proxy.ts — one port, both protocols
1import { startWsProxyServer } from "nucleus-core";2 3startWsProxyServer({4  port: 4000,5  http: {6    targets: [7      {8        url: process.env.IDP_URL!,9        paths: ["/auth/*", "/api/*"],10        injectTokenFromCookie: { cookieName: "access_token", headerName: "authorization" },11        tokenRefresh: { enabled: true, idpBaseUrl: process.env.IDP_URL!, retryOn401: true },12      },13    ],14  },15  ws: {16    targets: [17      {18        url: process.env.IDP_URL!,19        paths: ["/api/events/*"],20        injectTokenFromCookie: { cookieName: "access_token", queryParam: "token" },21      },22    ],23  },24});
port / hostnamenumber / stringOptional

Where the proxy listens. WS defaults to its own port when run separately.

Default4000 / 0.0.0.0
devbooleanOptional

Development mode — enables HMR pass-through and looser origin handling for local work.

httpHttpProxyConfigOptional

HTTP forwarding config: an array of targets plus onRequest/onResponse/onError hooks for logging and metrics.

wsWsProxyConfigOptional

WebSocket forwarding config: targets, optional HMR allowance, unknown-path rejection, ping interval and lifecycle hooks.

envPath / onReady / onErrorstring / functionsOptional

Optional env file to load, plus callbacks fired when the server is listening or errors.

Targets & routing#

Each target maps a set of glob paths to an upstream URL. paths are matched with glob-to-regex, pathRewrite reshapes the forwarded path, and changeOrigin rewrites the Host header. The same shape exists for HTTP and WS, with a few protocol-specific extras.

urlstringRequired

The upstream service this target forwards to.

pathsstring[]Required

Glob patterns that route to this target (e.g. '/auth/*', '/api/*'). First match wins.

Example: ["/auth/*", "/api/*"]

pathRewriteRecord<string, string>Optional

Rewrite matched paths before forwarding (e.g. strip a '/api' prefix the upstream doesn't expect).

changeOriginbooleanOptional

Rewrite the Host header to the target's origin — needed by many upstreams.

headersRecord<string, string>Optional

Static headers added to every forwarded request on this target.

Cookie → token injection & refresh#

This is why the proxy exists. injectTokenFromCookie lifts a token out of an httpOnly cookie and re-presents it as a header (HTTP) or query param (WS, which can't set headers from the browser). injectUserIdFromJwt decodes the JWT and forwards the user id. tokenRefresh transparently refreshes an expired token and retries the request.

injectTokenFromCookie.cookieNamestringOptional

The cookie to read the token from, with optional fallbackCookieNames for older names.

injectTokenFromCookie.headerName / queryParamstringOptional

Where to put the token downstream: a header for HTTP (e.g. 'authorization'), or a query param for WebSocket handshakes that can't carry custom headers.

injectUserIdFromJwt{ cookieName; headerName? }Optional

Decode the JWT in a cookie and forward its subject as a header — lets upstreams trust an identity without re-parsing tokens.

tokenRefresh.enabled / idpBaseUrlboolean / stringOptional

Turn on silent refresh and point it at the IDP. The proxy calls the refresh endpoint when needed.

tokenRefresh.retryOn401 / maxRefreshRetriesboolean / numberOptional

On a 401, refresh the token and replay the original request, bounded by maxRefreshRetries.

tokenRefresh.refreshCookieName / accessCookieNamestringOptional

Which cookies hold the refresh and access tokens, so rotated values can be re-set after a refresh.

Related sections