Expose a Web App and API with One Asyx Tunnel
You have an SPA (Single Page Application) on one port, an API on another, and a teammate who needs a single URL. This guide shows three patterns to route both services through one https://<name>.tunnel.asyx.ai origin so cookies, auth headers, and developer ergonomics behave like production.
Prerequisites
- SPA dev server on
localhost - API server on
localhost - Asyx CLI installed and enrolled (
asyx setup)
Examples below use:
- SPA Port →
5173 - API Port →
5000 - Optional gateway →
3000
Swap the numbers to match your project.
Pick a Pattern
- Option 1 – Use the SPA dev server as a proxy. Works when you already rely on Vite/CRA/Angular tooling.
- Option 2 – Add a lightweight reverse proxy. Ideal when the SPA server can’t be modified or you’re mixing frameworks.
- Option 3 – Serve the SPA from the API. Closest to a production deployment where the API hosts the compiled UI.
Choose whichever matches your workflow—only one pattern is required.
Option 1 — Let the SPA Dev Server Handle /api
Vite (React/Angular/Vanilla)
vite.config.ts
export default {
server: {
port: 5173,
proxy: {
"/api": {
target: "http://127.0.0.1:5000",
changeOrigin: true,
ws: true, // enable for WebSockets
},
},
},
};
Start Vite + the API, then tunnel the SPA port:
asyx tunnel --http --port 5173
Create React App
src/setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://127.0.0.1:5000",
changeOrigin: true,
ws: true,
}),
);
};
Run CRA (default 3000) and expose it:
asyx tunnel --http --port 3000
Angular CLI
proxy.conf.json
{
"/api": {
"target": "http://127.0.0.1:5000",
"secure": false,
"changeOrigin": true,
"ws": true,
"logLevel": "info"
}
}
angular.json snippet:
"serve": {
"options": {
"port": 4200,
"proxyConfig": "proxy.conf.json"
}
}
Start ng serve, then:
asyx tunnel --http --port 4200
Pros: no extra processes, hot reload intact, no CORS headers needed.
Reminder: call the API with relative paths such as /api/users.
Option 2 — Run a Lightweight Reverse Proxy
Use this when the SPA dev server is locked down or you want an explicit router between multiple services.
Minimal Node Gateway
gateway.js
import http from "node:http";
import { createProxyServer } from "http-proxy";
const proxyApi = createProxyServer({ target: "http://127.0.0.1:5000", ws: true });
const proxySpa = createProxyServer({ target: "http://127.0.0.1:5173", ws: true });
const server = http.createServer((req, res) => {
if (req.url.startsWith("/api/")) {
proxyApi.web(req, res);
} else {
proxySpa.web(req, res);
}
});
server.on("upgrade", (req, socket, head) => {
if (req.url.startsWith("/api/")) {
proxyApi.ws(req, socket, head);
} else {
proxySpa.ws(req, socket, head);
}
});
server.listen(3000, () => {
console.log("Gateway ready: http://127.0.0.1:3000 (SPA + API)");
});
Run the gateway alongside both services, then tunnel port 3000:
node gateway.js
asyx tunnel --http --port 3000
Nginx Equivalent
server {
listen 3000;
location /api/ {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://127.0.0.1:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Reload Nginx and tunnel port 3000.
Pros: clean separation of concerns, easy to add gzip/auth later, works with vendor dev servers.
Notes: remember to restart the proxy when you change routes.
Option 3 — Serve the SPA from the API
Build the SPA once and let the API process handle everything. This gives stakeholders a production-style experience.
Example Express Config
import express from "express";
const app = express();
const PORT = 5000;
app.use(express.static("dist")); // serve SPA bundle
app.get("/api/health", (_req, res) => {
res.json({ ok: true });
});
app.get("*", (_req, res) => {
res.sendFile(new URL("./dist/index.html", import.meta.url).pathname);
});
app.listen(PORT, () => {
console.log(`API + SPA listening on ${PORT}`);
});
Build your SPA (e.g. npm run build → dist/), run the API, then open the tunnel:
asyx tunnel --http --port 5000
Pros: single process, production parity, no proxy layer.
Cons: slower hot reload loops—best when you already bundle before testing.
Tips Before You Share the Link
- Use environment variables to point the SPA/API to
/apiso production and tunnel URLs match. - Keep the Asyx session running while you test—JWT refresh happens automatically.
- When you’re done,
Ctrl+Cstops the tunnel; restart with the same command to reuse your URL.