Skip to main content

no-floating-servers

Require Node.js server handles to be retained so they can be closed.

Rule catalog ID: R013

Targeted pattern scopeโ€‹

This rule targets server factory calls from Node.js core networking modules:

  • http.createServer
  • https.createServer
  • http2.createServer
  • http2.createSecureServer
  • net.createServer
  • equivalent named imports, namespace imports, default imports, destructured require(...) calls, and inline require(...).createServer(...) calls

The rule reports server handles that are immediately discarded, including discarded .listen(...) chains. It does not require type information because the recognized factories are tied to import and require sources.

What this rule reportsโ€‹

The rule reports:

  • standalone server creation such as createServer(handler);
  • voided server creation such as void http.createServer(handler);
  • discarded listen chains such as http.createServer(handler).listen(3000);
  • discarded method chains that start from an unretained server handle

It intentionally does not require same-function close() calls. Ownership can be transferred to a framework adapter, test harness, returned value, or shutdown manager.

Why this rule existsโ€‹

Node.js server factories create long-lived handles that can keep the process alive and hold open ports. Once a server is listening, retaining the Server object is the normal way to call .close(), coordinate graceful shutdown, and stop accepting new work.

Incorrectโ€‹

import { createServer } from "node:http";

createServer(handler);
import http from "node:http";

http.createServer(handler).listen(3000);
const http2 = require("http2");

http2.createSecureServer(options, handler).listen(8443);

Correctโ€‹

import { createServer } from "node:http";

const server = createServer(handler);

server.listen(3000);
server.close();
import * as http from "node:http";

const server = http.createServer(handler).listen(3000);

server.close();
return createServer(handler).listen(3000);
registerServer(createServer(handler).listen(3000));

Behavior and migration notesโ€‹

Store the returned server handle in the owner that will shut it down. In applications, that is usually the process lifecycle or graceful-shutdown coordinator. In tests, it is usually the test fixture cleanup hook.

This rule allows immediate .close() calls and returned or passed server handles. It reports discarded .listen(...) chains because listen() returns the server object; discarding that returned handle makes later shutdown coordination difficult.

This rule does not autofix. Choosing whether the owner is a module variable, dependency-injected lifecycle manager, framework adapter, or test fixture is a semantic decision.

ESLint flat config exampleโ€‹

import runtimeCleanup from "eslint-plugin-runtime-cleanup";

export default [
runtimeCleanup.configs.recommended,
];

When not to use itโ€‹

Do not enable this rule for one-off scripts where the server is intentionally process-lifetime and never needs graceful shutdown. Prefer a narrow disable comment with a reason in those files rather than weakening the rule globally.

Further readingโ€‹