Server lifecycle: start and stop

A server that starts but cannot stop is a problem you will discover the moment you try to write your first test. The test finishes, but Node keeps running because nobody told the listener to close. Suddenly your CI hangs forever and nobody knows why.

app.listen returns a Server object. That object has a .close() method. If you do not save the return value somewhere, you have no way to call .close() later. A tiny detail with a large blast radius.

after/index.ts
typescript
let serverInstance: Server | null = null;

export function startServer(port: number = 3000): Server {
  serverInstance = app.listen(port, () => {
    console.log('Server on :' + port);
  });
  return serverInstance;
}

export function stopServer() {
  if (serverInstance) {
    serverInstance.close();
  }
}

Save the server reference so you can shut it down cleanly.

In production this matters even more. When Kubernetes or your process manager wants to deploy a new version, it sends your process a signal asking it to shut down. A server that ignores the signal gets killed mid-request. A server that listens for it can finish in-flight work and close cleanly.

EADDRINUSE means another process is already using that port. Either find and kill it (lsof -i :3000 on macOS or Linux) or pick a different port. This happens to everyone. It is usually a leftover dev server you forgot to shut down.

Quiz: Quiz

Loading practice…