If you run more than two services locally, you already know the pain: was the
API on 3000 or 3001? Was that the docs site or the dashboard? Ports are an
implementation detail leaking straight into your muscle memory.
Named URLs replace that guessing game with something you can actually remember.
The problem with ports
Ports are allocated, not chosen. Your tooling grabs whatever is free, your
teammates get different numbers, and nothing is stable across restarts. A
README that says "open http://localhost:5173" is wrong the moment Vite
decides 5173 is taken.
Map a name to a port
With Portline you point a human-readable name at a port and forget the number ever existed:
# Give the running Vite dev server a stable local name
portline map dashboard --port 5173
# Now this always works, regardless of which port Vite grabbed
open http://dashboard.localhost
The mapping survives restarts and is shared through a small config file you can commit:
# portline.toml
[routes]
dashboard = 5173
api = 8080
docs = 4321
Resolve names in code
Because the names are deterministic, you can reference them directly instead of threading port numbers through environment variables:
// lib/services.ts
const services = {
api: "http://api.localhost",
docs: "http://docs.localhost",
} as const;
export const fetchUser = async (id: string) => {
const res = await fetch(`${services.api}/users/${id}`);
if (!res.ok) throw new Error(`user ${id} failed: ${res.status}`);
return res.json();
};
No more process.env.API_PORT ?? 8080 sprinkled across the codebase.
Why it matters
A name is a contract. A port is an accident.
Stable names make onboarding faster, READMEs accurate, and shared configs portable. The number was never the point — the service was.