Skip to main content

Guide

get-port vs detect-port vs portfinder 2026

Compare get-port, detect-port, and portfinder for finding available ports in Node.js. Free port detection, port ranges, dev server setup, and which port.

·PkgPulse Team·
0

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

Featureget-portdetect-portportfinder
API stylePromisePromise + callbackPromise + callback
Default behaviorRandom portScan from preferredScan from 8000
Port range✅ (portNumbers)✅ (stopPort)
Exclude ports
Host option
CLI
ESM-only
Dependencies012
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.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.