npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

portbridge

v0.1.0

Published

One command to run your frontend + backend dev servers behind a single unified proxy port — no CORS setup, merged logs, and a live request dashboard.

Downloads

10

Readme

Running a separate frontend (Next.js / Vite / CRA) and backend (Express / any Node server) locally means scattered ports, manual CORS config, logs split across terminals, and no visibility into requests crossing the front↔back boundary. portbridge fixes that:

  • 🔗 One unified port. The browser only ever talks to :4000. Requests to /api/* are proxied to your backend, everything else to your frontend — so your app needs zero CORS configuration.
  • 📜 Merged logs. Both servers' output, interleaved in one terminal, each line timestamped, prefixed, and color-coded ([web] / [api]).
  • 📊 Live dashboard (--dashboard). A real-time timeline of every request: method, path, target, status, and duration.
  • ♻️ Robust process control. Graceful Ctrl+C shutdown, optional auto-restart on crash, and clear errors when a port is taken.

Framework-agnostic: if it starts with a command and listens on a port, it works.


Getting started (Windows · macOS · Linux)

The commands are identical on every OS. Run them in your terminal — PowerShell or Windows Terminal on Windows, Terminal/iTerm on macOS, any shell on Linux.

1. Make sure you have Node.js 20+

Check your version:

node -v

If it prints v20 (or higher) you're good. If not, install Node LTS:

| OS | Easiest way | | ----------- | --------------------------------------------------------------------------------- | | Windows | nodejs.org installer, or winget install OpenJS.NodeJS.LTS | | macOS | nodejs.org installer, or brew install node | | Linux | your package manager, or nvm: nvm install 20 |

2. Create a config in your project

Go to your project's root folder (the one with your frontend and backend), then:

cd my-project
npx portbridge init

init auto-detects your stack (Next.js / Vite / CRA on the frontend; Express / Fastify / Koa / NestJS on the backend) and pre-fills the commands and ports — press Enter to accept each suggestion or type your own. It writes a portbridge.config.json:

{
  "frontend": { "command": "npm run dev", "port": 5173, "cwd": "./client" },
  "backend": { "command": "npm run server", "port": 5000, "cwd": "./server" },
  "proxy": { "port": 4000, "apiPrefix": "/api" }
}

command = how each server starts · port = where it listens · cwd = its folder. Adjust to match your project.

3. Start both servers

npx portbridge              # add --dashboard for the live request timeline

You'll get a banner with the unified URL and both servers' logs merged into one terminal.

4. Open your app

Open http://localhost:4000 in your browser. Your frontend loads, and any call it makes to /api/... is forwarded to your backend on the same origin — so there's no CORS to configure. Press Ctrl+C to stop everything.

Use it everywhere: install once with npm install -g portbridge, then just run portbridge (instead of npx portbridge) in any project.

Configuration

portbridge reads its config from the current directory (or --config <path>). Supported formats: portbridge.config.{json,js,mjs,cjs,ts} — JS/TS configs are loaded via jiti, so you can use comments and computed values.

{
  "frontend": { "command": "npm run dev", "port": 5173, "cwd": "./client" },
  "backend": { "command": "npm run server", "port": 5000, "cwd": "./server" },
  "proxy": { "port": 4000, "apiPrefix": "/api" },
  "restartOnCrash": false
}

| Field | Meaning | | ----------------------------------- | ------------------------------------------------------------------- | | frontend / backend .command | shell command that starts the server | | frontend / backend .port | the port that server listens on (proxy forwards here) | | frontend / backend .cwd | working directory for the command (relative to the config) | | frontend / backend .host | host the proxy connects to (default localhost; reaches IPv4/IPv6) | | frontend / backend .env | extra environment variables for that process (object) | | frontend / backend .readyPath | health path to poll for wait-for-ready (e.g. /health) | | frontend / backend .name | optional log label (defaults: web / api) | | proxy.port | the single unified port you open in the browser | | proxy.host | interface the proxy binds to (default 127.0.0.1) | | proxy.apiPrefix | requests under this path go to the backend (default /api) | | restartOnCrash | auto-restart a server if it exits non-zero (default false) |

PORT is injected. portbridge sets PORT=<the configured port> in each server's environment, so servers that read process.env.PORT bind the right port automatically. Otherwise, make the server listen on the port you configure (e.g. Vite's server.port). A per-service env value wins over the injected PORT.

CLI

portbridge [start] [options]     # "start" is the default command
portbridge init [options]

Start options:
  -c, --config <path>   path to portbridge.config.json
  -p, --port <number>   unified proxy port (overrides config)
  -H, --host <host>     interface to bind (default 127.0.0.1; use 0.0.0.0 for LAN)
  -d, --dashboard       open a live request-timeline dashboard
  -v, --verbose         print [portbridge] diagnostics about the startup sequence
      --no-proxy        run servers + merged logs without the proxy
      --no-wait         don't wait for the servers to start listening first
      --strict-port     fail if the proxy port is taken (default: auto-pick free)
      --no-env-check    skip the .env.example vs .env check
  -V, --version
  -h, --help

Init options:
  -y, --yes             write defaults without prompting
  -f, --force           overwrite an existing config

Env:
  PORTBRIDGE_NO_OPEN=1  don't auto-open the dashboard browser (CI/headless)

How routing works

browser ──▶ http://localhost:4000   (the only origin your app knows)
                 │
                 ├─ /api/*     ──▶  backend   (http://127.0.0.1:5000)
                 └─ everything ──▶  frontend  (http://127.0.0.1:5173)

Because the browser talks to a single origin, there are no cross-origin requests — no CORS headers to configure. WebSocket upgrades (e.g. Vite/Next HMR) are forwarded to the frontend automatically.

What it looks like

  portbridge  ·  one port for your whole stack

  Open       http://localhost:4000
  Dashboard  http://localhost:4000/_portbridge
  Frontend   npm run dev     → :5173
  Backend    npm run server  → :5000  /api/*

  Press Ctrl+C to stop.

[10:24:01] [web] VITE ready in 412 ms
[10:24:01] [api] API listening on http://localhost:5000
[10:24:07] [api] GET /api/todos 200 18ms

Frontend lines are cyan, backend magenta, errors red — all interleaved in one terminal, in real time. With --dashboard, the same requests stream into a live timeline at /_portbridge.

Why not just concurrently + CORS?

concurrently "npm run client" "npm run server" runs both processes, and that's it. You still:

  • configure CORS on your backend (or a framework proxy) so the browser can call the API from a different port;
  • juggle two URLs/ports and keep them straight;
  • read interleaved-but-unlabelled logs with no per-line source or timing;
  • have no view of requests crossing the front↔back boundary.

portbridge gives you one origin (no CORS), labelled/timed merged logs, a live request dashboard, plus quality-of-life guards (port auto-resolution, .env check, wait-for-ready, crash restart). It's the "run both servers" step plus the glue you'd otherwise wire by hand.

Try the example

A runnable, dependency-free demo lives in example/ — a tiny todo app. From that folder:

node ../bin/portbridge.js --dashboard   # or `npx portbridge --dashboard`

…then open http://localhost:4000 and http://localhost:4000/_portbridge.

Notes

  • ESM-only. This is a modern Node ≥20 CLI; two core dependencies (execa, chalk) are pure ESM, so there is no CommonJS build.
  • The dashboard is served at /_portbridge on the unified port; that path is reserved (never proxied to your app).
  • Local by default. The proxy binds 127.0.0.1, so your app and dashboard aren't exposed on the network. Use --host 0.0.0.0 (or proxy.host) to allow access from other devices.
  • IPv4/IPv6. Upstream hosts default to localhost and are reached with Happy Eyeballs, so a server bound to 127.0.0.1 or ::1 works. Force a stack with a service host of 127.0.0.1 or ::1.

Platform support

Developed and tested on macOS and Linux, and exercised on Windows in CI (Node 20 & 22 across all three via GitHub Actions). On POSIX, portbridge signals the whole process group on shutdown; on Windows it uses taskkill /T to stop the process tree.

Development

npm install
npm test          # vitest (unit + integration + e2e)
npm run lint      # eslint
npm run typecheck # tsc --noEmit
npm run build     # tsup -> dist/

License

MIT