TL;DR
get-port is the minimal port finder — gets a random available port or checks specific ports, tiny (no dependencies), async, by Sindre Sorhus. detect-port is Alibaba's port detector — finds an available port starting from a preferred number, used by Umi and Egg.js, returns the next free port. portfinder is the classic port finder — scans from a base port upward, callback and promise API, the most widely used. In 2026: get-port for minimal port detection, detect-port for auto-incrementing port fallback, portfinder for legacy projects.
Key Takeaways
- get-port: ~10M weekly downloads — minimal, random or specific, by Sindre Sorhus
- detect-port: ~10M weekly downloads — Alibaba, auto-increment, fallback port
- portfinder: ~10M weekly downloads — classic, base port scanning, callback/promise
- All three find available TCP ports on localhost
- get-port is the most modern (ESM-only, no dependencies)
- detect-port and portfinder scan upward from a preferred port
get-port
get-port — minimal port finder:
Basic usage
import getPort from "get-port"
// Get a random available port:
const port = await getPort()
console.log(port) // → 52741 (random available port)
// Prefer a specific port, fallback to random:
const port2 = await getPort({ port: 3000 })
console.log(port2) // → 3000 if available, or random
// Prefer from a list of ports:
const port3 = await getPort({ port: [3000, 3001, 3002] })
console.log(port3) // → First available from list, or random
Port range
import getPort, { portNumbers } from "get-port"
// Get port from a range:
const port = await getPort({ port: portNumbers(3000, 3100) })
console.log(port) // → First available in 3000-3100, or random
// Use in a dev server:
import express from "express"
const app = express()
const port = await getPort({ port: portNumbers(3000, 3010) })
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
})
Make a range of ports
import getPort, { portNumbers } from "get-port"
// portNumbers returns an iterable:
const ports = portNumbers(8000, 8100)
const port = await getPort({ port: ports })
// Exclude specific ports:
const port2 = await getPort({
port: portNumbers(3000, 3100),
exclude: [3001, 3005],
})
// Host-specific:
const port3 = await getPort({
port: 3000,
host: "0.0.0.0", // Check on all interfaces
})
Dev server pattern
import getPort, { portNumbers } from "get-port"
import { createServer } from "node:http"
async function startServer() {
const port = await getPort({ port: portNumbers(3000, 3010) })
const server = createServer((req, res) => {
res.end("Hello!")
})
server.listen(port, () => {
console.log(`→ http://localhost:${port}`)
})
return { server, port }
}
detect-port
detect-port — auto-increment port:
Basic usage
import detect from "detect-port"
// Find available port starting from 3000:
const port = await detect(3000)
console.log(port)
// → 3000 if available
// → 3001 if 3000 is taken
// → 3002 if 3001 is also taken
// → etc.
// Default (starts from 1):
const port2 = await detect()
With callback
import detect from "detect-port"
// Callback style:
detect(3000, (err, port) => {
if (err) {
console.error(err)
return
}
if (port === 3000) {
console.log("Port 3000 is available")
} else {
console.log(`Port 3000 is taken, using ${port} instead`)
}
})
User prompt pattern
import detect from "detect-port"
import readline from "readline"
async function getServerPort(preferred: number): Promise<number> {
const available = await detect(preferred)
if (available === preferred) {
return preferred
}
// Port is taken — ask user:
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
return new Promise((resolve) => {
rl.question(
`Port ${preferred} is in use. Use ${available} instead? (Y/n) `,
(answer) => {
rl.close()
resolve(answer.toLowerCase() === "n" ? preferred : available)
}
)
})
}
// Usage:
const port = await getServerPort(3000)
// → "Port 3000 is in use. Use 3001 instead? (Y/n)"
CLI usage
# CLI:
npx detect-port 3000
# → 3000 (if available)
# → 3001 (if 3000 is taken)
# Check specific port:
npx detect-port 8080
portfinder
portfinder — classic port scanner:
Basic usage
import portfinder from "portfinder"
// Find available port (default starts at 8000):
const port = await portfinder.getPortPromise()
console.log(port) // → 8000 (or next available)
// Start from specific port:
const port2 = await portfinder.getPortPromise({ port: 3000 })
console.log(port2) // → 3000 or next available
// With options:
const port3 = await portfinder.getPortPromise({
port: 3000,
stopPort: 3100, // Don't scan beyond 3100
host: "127.0.0.1",
})
Callback style
import portfinder from "portfinder"
// Set base port:
portfinder.basePort = 3000
// Set highest port to scan:
portfinder.highestPort = 3100
portfinder.getPort((err, port) => {
if (err) {
console.error("No available port found:", err)
return
}
console.log(`Available port: ${port}`)
})
Multiple ports
import portfinder from "portfinder"
// Get multiple consecutive ports:
const ports = await portfinder.getPortPromise({ port: 3000 })
.then(async (port1) => {
const port2 = await portfinder.getPortPromise({ port: port1 + 1 })
return [port1, port2]
})
console.log(ports) // → [3000, 3001] (or next available pair)
With dev servers
import portfinder from "portfinder"
import { createServer } from "vite"
async function startViteDev() {
const port = await portfinder.getPortPromise({ port: 5173 })
const server = await createServer({
server: { port },
})
await server.listen()
console.log(`Vite dev server: http://localhost:${port}`)
}
Feature Comparison
| Feature | get-port | detect-port | portfinder |
|---|---|---|---|
| API style | Promise | Promise + callback | Promise + callback |
| Default behavior | Random port | Scan from preferred | Scan from 8000 |
| Port range | ✅ (portNumbers) | ❌ | ✅ (stopPort) |
| Exclude ports | ✅ | ❌ | ❌ |
| Host option | ✅ | ✅ | ✅ |
| CLI | ❌ | ✅ | ❌ |
| ESM-only | ✅ | ❌ | ❌ |
| Dependencies | 0 | 1 | 2 |
| TypeScript | ✅ | ✅ | ✅ (@types) |
| Maintained | ✅ (Sindre Sorhus) | ✅ (Alibaba) | ⚠️ |
| Weekly downloads | ~10M | ~10M | ~10M |
When to Use Each
Use get-port if:
- Want a minimal, zero-dependency port finder
- Need random port assignment or port ranges
- Building modern ESM projects
- Want to exclude specific ports
Use detect-port if:
- Want auto-incrementing port fallback (3000 → 3001 → 3002)
- Need a CLI for quick port checks
- Building tools where user confirmation of port change is needed
- In the Alibaba/Egg.js ecosystem
Use portfinder if:
- Need legacy callback support
- Want configurable port scan range (base + stop)
- Existing project already uses it
- Need a battle-tested port finder
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on get-port v7.x, detect-port v2.x, and portfinder v1.x.
Race Conditions and Port Reservation Semantics
All three libraries solve the same fundamental problem — finding a TCP port that isn't currently bound — but they have subtly different semantics around race conditions that matter in specific contexts. The core limitation shared by all three is the TOCTOU (Time-Of-Check-Time-Of-Use) problem: the library checks whether a port is free, returns it to you, and then you bind your server. Between the check and the bind, another process could claim that port. In practice this is rare in single-developer environments, but in CI environments where multiple test suites run in parallel it's a real source of flaky tests.
get-port's approach to this is pragmatic: it prefers your specified port list but falls back to a random OS-assigned port (port 0 technique) if all specified ports are taken. The OS-assigned random port is the most reliable approach since the OS atomically allocates it when the socket is bound. If you need guaranteed uniqueness, request getPort() with no port preference — this uses the OS directly and eliminates the race entirely.
detect-port's sequential scanning (3000 → 3001 → 3002) is more prone to collision in parallel test runners since all test processes might scan upward from the same base port simultaneously. If you're running Jest/Vitest with multiple workers and each worker calls detect(3000), they could all find port 3000 available (before any of them bind it) and then fail when they try to bind. get-port's random fallback avoids this pattern.
For CI pipelines running parallel test suites, the recommended pattern is to not specify a preferred port at all and use whatever the OS assigns: const port = await getPort() then pass that port to your server. Commit this pattern as a utility in your test helpers rather than scattering port-finding logic across test files.
Integration with Dev Server Tooling
These libraries are most commonly encountered not in direct usage but as internal dependencies of the tools you already use. Understanding where each library appears in the ecosystem helps explain their download counts and informs which one to choose when you need the capability directly.
Vite uses get-port internally for its dev server port selection — when you configure server.port: 3000 in vite.config.ts and that port is taken, Vite falls back to the next available port using get-port's logic. Webpack Dev Server uses portfinder for the same purpose. This is a significant chunk of both libraries' download counts: most projects aren't installing them directly but pulling them in transitively through their bundler.
detect-port appears most prominently in the Alibaba/Ant Design ecosystem: Umi (the React framework behind many enterprise Chinese web apps), Egg.js, and related tools all use detect-port for their dev server startup. The library's auto-increment approach fits this use case well — when a developer is running multiple services locally, they want the next service to start on the next available port with a clear message about which port was substituted, which detect-port's callback API supports natively with the "Port 3000 is in use. Use 3001 instead?" prompt pattern.
When choosing between these libraries for a CLI tool or dev server you're building, consider what UX you want when the preferred port is taken. detect-port's explicit notification approach is appropriate when the user specified the port and should know it changed. get-port's silent random fallback is appropriate for internal tooling where the exact port number doesn't matter. portfinder's configurable range (base + stopPort) is appropriate when the port needs to be predictably within a specific range for firewall or documentation reasons.
Compare networking utilities and developer tooling on PkgPulse →
Testing Strategies and Port Management in Test Suites
Port management becomes a first-class concern when your test suite spins up real HTTP servers for integration testing. The naïve approach — hardcoding port 3000 in every test file — leads to test failures the moment two test files run concurrently. The correct approach requires thinking about port lifecycle: allocation before tests, binding during tests, and release after tests.
The cleanest pattern for Jest or Vitest is allocating ports in beforeAll and binding them just before the test server starts. Using await getPort() (with no preferred port) in beforeAll gives each test file its own random OS-assigned port, guaranteeing no collisions. Store the port in a module-level variable and reference it in afterAll to close the server. Avoid allocating in beforeEach — you'd be finding a new port for every individual test, which adds latency and makes log output harder to correlate.
For test suites that spawn multiple services (a common pattern when testing microservice interaction), portNumbers from get-port lets you request multiple ports from a range while tracking which are already claimed:
import getPort, { portNumbers } from "get-port"
// In beforeAll:
const appPort = await getPort({ port: portNumbers(4000, 4100) })
const dbProxyPort = await getPort({ port: portNumbers(4000, 4100), exclude: [appPort] })
const mockServerPort = await getPort({ port: portNumbers(4000, 4100), exclude: [appPort, dbProxyPort] })
portfinder's getPortPromise with an incrementing port option can accomplish the same thing, but it scans sequentially and doesn't support explicit exclusion lists, making it harder to guarantee non-overlapping allocations when multiple async processes run simultaneously. For test environments, get-port's exclude list is the safer pattern.
When to Use Each
Use get-port if:
- You want the simplest, most modern API for finding an available port
- You are building a dev server, CLI tool, or test helper in Node.js
- You want TypeScript types included and ESM support
- You want to specify a preferred port with automatic fallback
Use detect-port if:
- You are in the Alibaba/taobao ecosystem (create-react-app, umi, ice)
- You need the non-breaking behavior: if port N is taken, suggest N+1 or N+2
- You want a quick detection call that returns a suggestion rather than finding an arbitrary free port
Use portfinder if:
- You are working in a legacy Node.js project that already uses portfinder
- You need a battle-tested package with a long track record
- You want the scan-from-a-base-port behavior in a traditional callback or Promise API
In 2026, get-port is the right choice for new projects. It is actively maintained by Sindre Sorhus, ESM-native, and has the cleanest API of the three. The detect-port package is primarily used in the Alibaba-originated tooling ecosystem. portfinder is a legacy choice that still works but gets less active development attention.
For CI environments, always test with a randomized port rather than a fixed fallback port — parallel test runs on the same host can collide on the same default port, causing flaky tests.
See also: cac vs meow vs arg 2026 and Ink vs @clack/prompts vs Enquirer, acorn vs @babel/parser vs espree.