Connection reliability

Handle end, close, and errors

Distinguish a peer half-close, final socket closure, and an exceptional failure.

10 minute lesson

~~~

A TCP connection can finish several ways, and Node reports each with a distinct event. Handling all of them cleanly is the difference between a server that runs for months and one that slowly leaks state.

The end event means the readable side received FIN — the peer announced it will send no more data. TCP allows a half-close: the socket may still be writable, so you can send a final reply after the client finished sending. In Node that only happens if you create the server with allowHalfOpen: true; by default Node closes the writable side back automatically.

The close event means the handle is fully gone. It fires exactly once for every connection, however it ended. error carries a failure, and close follows it with hadError set to true.

Log the lifecycle:

socket.on('end', () => console.log('peer ended writes'))
socket.on('close', hadError => console.log('closed', { hadError }))
socket.on('error', error => console.error(error.code))

Watch each ending

Close a client normally — quit nc with Ctrl-C, which makes the kernel send FIN:

peer ended writes
closed { hadError: false }

Now force an abrupt reset. Have a test client call socket.resetAndDestroy(), which sends RST instead of FIN:

ECONNRESET
closed { hadError: true }

ECONNRESET also shows up in the wild when a peer crashes with data in flight or a middlebox kills the connection. Finally, stop the server while a client is connected and watch the client side see end — closure looks graceful from the other end too.

Compare event order across runs: error comes first when present, end appears only on graceful closes, and close is always last.

Clean up on close, not on end

Per-connection resources — timers, entries in a clients map, pending work — belong in the close handler:

socket.on('close', () => {
  clearTimeout(deadline)
  clients.delete(socket)
})

Cleanup timers and per-client state on close. Do not assume every connection reaches the happy-path end event: resets, crashes, and your own socket.destroy() calls all skip it. State you only release on end leaks until the process restarts, and close is the one event guaranteed to fire.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →