You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Koa集成GraphQL时nodemon重启报3000端口占用如何修复

问题现象

给KoaJS服务接入GraphQL能力时,nodemon监听到文件变更触发服务重启,控制台持续抛出端口占用错误:

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (node:net:1334:16)
    at listenInCluster (node:net:1382:12) 
  ...
  code: 'EADDRINUSE',
  errno: -48,
  syscall: 'listen',
  address: '::',
  port: 3000
}
[nodemon] app crashed - waiting for file changes before starting...

原有问题代码如下:

import { ApolloServer, gql } from "apollo-server-koa";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
import Koa from "koa";
import http from "http";

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => "Hello world!",
  },
};

async function startApolloServer() {
  const httpServer = http.createServer();
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    csrfPrevention: true,
    cache: "bounded", 
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  await server.start();
  const app = new Koa();
  server.applyMiddleware({ app, path: "/graphql" });
  httpServer.on("request", app.callback());
  await new Promise<void>((resolve) =>
    httpServer.listen({ port: 3000 }, resolve)
  );
  console.log(`🚀 Server ready at http://localhost:3000${server.graphqlPath}`);
  return { server, app };
}

startApolloServer()
问题原因

报错核心是旧服务进程没有被正常终止:nodemon触发重启时会给原有Node进程发送终止信号,但代码里没有配置优雅关停逻辑,旧进程持有的3000端口没有被主动释放,新进程启动时尝试监听同个端口就会触发地址占用错误。
另外原代码中HTTP服务绑定Koa回调的写法不规范,也可能造成请求句柄泄漏,拖慢端口释放速度。

修复方法
  • 修正HTTP服务初始化逻辑,创建服务时直接传入Koa实例的回调,移除单独绑定request事件的写法
  • 新增进程终止信号监听,收到nodemon发送的SIGINT/SIGTERM信号时,主动关停Apollo服务与HTTP服务,确保端口被及时释放

修复后的完整代码:

import { ApolloServer, gql } from "apollo-server-koa";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
import Koa from "koa";
import http from "http";

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => "Hello world!",
  },
};

async function startApolloServer() {
  const app = new Koa();
  const httpServer = http.createServer(app.callback());
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    csrfPrevention: true,
    cache: "bounded", 
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  await server.start();
  server.applyMiddleware({ app, path: "/graphql" });
  
  await new Promise<void>((resolve) =>
    httpServer.listen({ port: 3000 }, resolve)
  );
  console.log(`🚀 Server ready at http://localhost:3000${server.graphqlPath}`);

  // 优雅关停逻辑
  const shutdown = async () => {
    await server.stop();
    httpServer.close(() => {
      process.exit(0);
    });
  };
  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);

  return { server, app, httpServer };
}

startApolloServer()

临时排查时如果端口已经被残留僵尸进程占用,可以执行对应命令手动释放端口后再重启服务:

  • Mac/Linux 系统:执行 lsof -ti:3000 | xargs kill -9
  • Windows 系统(PowerShell):执行 Stop-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess -Force

内容的提问来源于stack exchange,提问作者retr0327

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:27:18