Dev Notes

Productivity 6 min read

Port 3000 Is Already in Use. Of Course.

PortChop is my small macOS app for finding out what is using a port and stopping the process behind it. Here is the thinking behind the tool.

You start a development server. Port 3000 is already in use.

By what? Probably another development server. Possibly the one you were convinced you stopped. It has apparently renewed its lease.

This is a small problem. The terminal can solve it, and switching to another port may be perfectly fine. But it is also a useful example of the kind of friction that makes a small tool worth building.

Meet PortChop

PortChop is my macOS utility for inspecting port usage, identifying the processes behind it, and freeing occupied ports. It uses Swift, SwiftUI, and macOS system APIs, and runs locally.

You can search by port, process name, or PID, pin frequently used ports, and access the app from the menu bar. The point is to get from “something is using this port” to a process you recognise, without assembling the same terminal commands each time.

You can find screenshots and download options on the PortChop website. Here is the design problem behind that small interface.

First, find out who lives there

An EADDRINUSE error means the server could not bind to the requested local address because it was already occupied. The Node.js error documentation describes that case. It does not tell you whether the existing process is yesterday’s experiment or something you still need.

For a TCP listener on port 3000, this is a useful first look on macOS:

lsof -nP -iTCP:3000 -sTCP:LISTEN

The flags keep addresses and ports numeric, select TCP port 3000, and filter for listening sockets. The lsof manual documents those options.

A simplified example might look like this:

COMMAND   PID   USER   ...   NAME
node     4242   you    ...   127.0.0.1:3000 (LISTEN)

That is already useful: there is a listener, and its process ID is 4242. But node is a runtime, not a project name. If you have several projects open, it leaves the most interesting question unanswered.

You can inspect the command behind that example PID:

ps -p 4242 -o pid=,ppid=,command=

Use the PID from your own output, of course. 4242 has no special powers.

The terminal route works. It also exposes the design problem: a port number leads to a process, and the process needs enough context for you to decide what to do next.

Make the decision readable

A port-management interface can easily become a spreadsheet of operating-system facts. More columns do not automatically make the decision easier.

The questions worth putting first are fairly ordinary:

  • Which port is occupied?
  • Which process owns the listener?
  • Can I recognise what that process belongs to?
  • What will happen if I stop it?

A useful row gives the port, process name, and PID a clear hierarchy. The full command or other available context can sit one level deeper. Those details matter when several processes share a name, but they do not all need equal visual weight.

Missing information deserves honest treatment, too. If a tool cannot determine the project behind a process, “unknown” is more useful than a confident guess. node alone is not evidence that a process belongs to the frontend you currently have open.

For a utility like PortChop, this is where interface design earns its keep: reducing the work between seeing a conflict and understanding it.

A free port is an outcome, not a button press

“Free port” sounds tidy. Underneath, something has to stop listening.

That distinction should stay visible. Stopping a process can affect more than one port, and a process managed by another application may simply restart. A successful stop request is not proof that the port is now available.

My preferred interaction for this kind of tool has three steps: identify the process, request that it stop, then refresh the actual state. A normal termination request should come before a forced stop. Force should be an explicit escalation, because it removes the process’s opportunity to handle shutdown.

When the original terminal or application is still available, stopping the service there is often the clearest option. A port utility should help you make that choice, too.

The feedback needs to be specific. “Process stopped” and “Port is still occupied” can both be true. So can “Permission denied.” Replacing all three with a cheerful success message would make the interface simpler, briefly.

When does a small tool earn its place?

Not every repeated command needs an app. A shell alias can be the complete solution, and maintaining an application costs more than writing the first version.

I find the case more convincing when the same task repeatedly involves finding information, interpreting it, and making a small decision. The command is only one part of that sequence. Remembering which terminal belongs to which project is another.

That is the appeal of PortChop’s scope. The workflow has a clear beginning and end: a port is occupied; find its owner; decide whether to leave it alone or stop it; get back to work.

It does not need an account. It does not need to send process information to a server. Running locally fits the job, because the relevant information is already on the machine.

The measure of success is modest: can someone resolve the interruption without creating a second one?

Keep the edges of the tool intact

Once a utility exists, nearby feature ideas start arriving. CPU charts. Container controls. Logs. A terminal. Perhaps a dashboard for the other dashboards.

Some of those could be useful. Each would also introduce new states, permissions, and maintenance work. They deserve their own justification rather than getting in because there is space in the sidebar.

For PortChop, I would judge an addition against the original task: does it help identify the owner of a port, understand the consequences of stopping it, or verify the result? If it does, it belongs in the discussion. If it does not, leaving it out is a reasonable decision.

Small tools still need careful error handling, readable information, and sensible behaviour when the system changes underneath them. Keeping the feature list short leaves room for that work.

Then the tool can close, the development server can start, and port 3000 can return to its demanding schedule of serving a page with three unfinished buttons.

Take a look at PortChop: portchop.tosa.io.

  • #PortChop
  • #macOS
  • #Developer Tools
  • #Product Design
Share LinkedIn X
A blue creature relaxes in a beach chair with a drink

Ready to start?

Tell me what you want to build or improve on an existing website.