Skip to main content

Asyx is invite-only while we finish the first public release.

Request Access

Docs

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 builddist/), 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 /api so production and tunnel URLs match.
  • Keep the Asyx session running while you test—JWT refresh happens automatically.
  • When you’re done, Ctrl+C stops the tunnel; restart with the same command to reuse your URL.