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

求助:Apollo 4 GraphQL对接Elasticsearch返回NULL问题

Apollo 4 GraphQL服务创建任务返回NULL问题排查

我正在搭建基于Apollo 4的GraphQL服务,实现与Elasticsearch的数据交互,但发送创建任务的POST请求时始终返回NULL。Elasticsearch与Apollo的连接看似正常,以下是核心文件代码、请求及返回内容,恳请帮忙排查问题:


server.js(服务启动文件)

const express = require('express')
const app = express()
const tasks = require('./routes/tasks')
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone')
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { schema } = require('./schema.js');
const { resolvers } = require('./resolvers.js');
const http = require('http');
const cors = require('cors');
const { json } = require('body-parser');
const { expressMiddleware } = require('@apollo/server/express4');

app.use(express.json())
app.use(express.urlencoded({ extended: false }))

const httpServer = http.createServer(app);
const server = new ApolloServer({
  schema,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

const startServer = async () => {
  await startStandaloneServer(server, {
    listen: { port: 5555 },
  });
  console.log(`Server ready at: localhost:5555`);
  console.log('Query at: https://studio.apollographql.com/dev');

  app.use(
    '/graphql',
    cors(),
    json(),
    expressMiddleware(server, {
      context: async ({ req }) => ({ token: req.headers.token }),
    })
  );

  app.listen(5551, () => {
    console.log('port: 5551');
  });
};

startServer();

tasks.js(路由文件)

const express = require('express')
const router = express.Router()
const {getTasks,addTasks,changeTasks,deleteTasks,updateTaskDone} = require('../controllers/tasks')
router.use(express.json())
router.use(express.urlencoded({extended:false}))

router.get('/', getTasks);
router.post('/', addTasks);
router.put('/done/:id', updateTaskDone);
router.delete('/delete/:id', deleteTasks);
router.put('/rename/:id',changeTasks)

module.exports = router;

tasks.js(控制器文件)

const {Client} = require('@elastic/elasticsearch')

const client = new Client({
  node: 'https://localhost:9200',
  auth: {
      username:"elastic",
      password:"rphcDrBrEm_i1D7_A+hS",
  },
  tls: {
      rejectUnauthorized: false
    }
})

async function run(){
  console.log("connected to elastic!");
  
}

run().catch(errore =>{
  console.log("errore");
})

let tasks = []

const getTasks =  async (req, res) => {
  const result = await client.search({
    index: 'tasks',
    body:{
      query:{
        match_all: {}
      }
    }
  })
  res.status(200).json({success:true, tasks:tasks})
  }

  
  const addTasks = async (req, res) => {
    const { name } = req.body;
    const newTask = { name: name, done: false };
    const result = await client.index({
      index: 'tasks',
      body: newTask
    });
    const { _id } = result;
    newTask._id = _id;
    console.log("Task creata con successo, ID:",_id)
    tasks.push(newTask);
    res.status(201).json(newTask);
  }
  

  const updateTaskDone = async (req, res) => {
    const { id } = req.params;
    const taskIndex = tasks.findIndex(task => task._id === id);
    const tasksToUpdate = [...tasks];
      
    if (tasksToUpdate[taskIndex].done) {
      await client.update({
        index: 'tasks',
        id: id,
        body: {
          doc: {
            done: false
          }
        }
      });
      tasksToUpdate[taskIndex].done = false;
      }else {
      await client.update({
        index: 'tasks',
        id: id,
        body: {
          doc: {
            done: true
          }
        }
      });
      tasksToUpdate[taskIndex].done = true;
    }
    console.log("Task aggiornata con successo, ID:",id)
    tasks = tasksToUpdate;
    res.status(200).json({ success: true, task: tasks });
};
  

const changeTasks = async (req, res) => {
  const { id } = req.params;
  const tasksToUpdate = [...tasks];
  const { newName } = req.body;
  const taskIndex = tasks.findIndex(task => task._id === id);
  await client.update({
    index: 'tasks',
    id: id,
    body: {
      doc: {
        name: newName
      }
    }
  });
  tasksToUpdate[taskIndex].name = newName;
  tasks = tasksToUpdate;
  res.status(200).json({ success: true, task: tasks });
};



const deleteTasks = async (req, res) => {
  const { id } = req.params;
  const taskIndex = tasks.findIndex(task => task._id === id);
  const tasksToUpdate = [...tasks];
  const taskToDelete = tasks.splice(tasksToUpdate[taskIndex], 1);
  await client.delete({
    index: 'tasks',
    id: id,
  });
  const updatedTasks = [...tasks];
  tasks = updatedTasks;
  res.status(200).json({ success: true, tasks: updatedTasks  });
  console.log("Task eliminata con successo, ID:",id)
};


module.exports = {getTasks,addTasks,changeTasks,deleteTasks,updateTaskDone,client}

schema.js

const { makeExecutableSchema } = require('@graphql-tools/schema');

const typeDefs = `#graphql

    type Query {
        tasks: [Task!]!
    } 

    type Mutation{
        newTask( input: taskInput): Task
    }

    input taskInput{
        name: String!,
        done: Boolean!,
    }

    type Task {
        name: String!
        done: Boolean!
        _id: ID!
    }
`
const schema = makeExecutableSchema({ typeDefs });
module.exports = {schema}

resolvers.js

const { client } = require('./controllers/tasks');

const resolvers = {
  Query: {
    tasks: async (parent, args, context, info) => {
      const { body } = await client.search({
        index: 'tasks',
        body: {
          query: {
            match_all: {}
          }
        }
      });

      const tasks = body.hits.hits.map(hit => hit._source);

      return tasks;
    }
  },

  Mutation: {
    newTask: async (parent, args, context, info) => {
      const { input } = args;
      const newTask = {
        name: input.name,
        done: false
      };

      const result = await client.index({
        index: 'tasks',
        body: newTask
      });

      const { _id } = result;
      newTask._id = _id;

      return newTask;
    }
  }
};

module.exports = { resolvers };

请求内容(Postman发送)

{
  "query": "mutation newTask($input: taskInput) { newTask(input: $input) { _id name done } }",
  "variables": {
    "input": {
      "done": false,
      "name": "lol123123123"
    }
  }
}

返回结果

{
    "data": {
        "newTask": null
    }
}

问题排查与修复方案

1. 修复Apollo Server启动冲突问题

你的server.js同时启动了两个独立服务:startStandaloneServer占用5555端口,又通过app.listen启动了5551端口的Express服务,导致GraphQL请求可能被路由到错误的服务实例。统一启动方式:

const express = require('express')
const app = express()
const tasks = require('./routes/tasks')
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { schema } = require('./schema.js');
const { resolvers } = require('./resolvers.js');
const http = require('http');
const cors = require('cors');
const { json } = require('body-parser');

app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use('/tasks', tasks); // 挂载REST路由

const httpServer = http.createServer(app);
const server = new ApolloServer({
  schema,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

const startServer = async () => {
  await server.start(); // 先启动Apollo核心服务

  // 挂载GraphQL中间件到Express
  app.use(
    '/graphql',
    cors(),
    json(),
    expressMiddleware(server, {
      context: async ({ req }) => ({ token: req.headers.token }),
    })
  );

  // 启动统一HTTP服务
  await new Promise(resolve => httpServer.listen({ port: 5555 }, resolve));
  console.log(`Server ready at http://localhost:5555/graphql`);
};

startServer();

2. 修复Resolver返回值不匹配Schema问题

你的Query和Mutation resolver返回的任务对象缺少_id字段(仅返回Elasticsearch的_source),但Schema要求Task类型必须包含_id,Apollo会因字段缺失将结果置为null。修改Resolver:

const { client } = require('./controllers/tasks');

const resolvers = {
  Query: {
    tasks: async (parent, args, context, info) => {
      try {
        const { body } = await client.search({
          index: 'tasks',
          body: {
            query: {
              match_all: {}
            }
          }
        });

        // 把Elasticsearch的文档ID注入返回对象
        return body.hits.hits.map(hit => ({
          ...hit._source,
          _id: hit._id
        }));
      } catch (error) {
        console.error('获取任务失败:', error);
        throw new Error('无法获取任务');
      }
    }
  },

  Mutation: {
    newTask: async (parent, args, context, info) => {
      try {
        const { input } = args;
        const newTask = {
          name: input.name,
          done: input.done || false // 尊重传入的done值,或默认false
        };

        const result = await client.index({
          index: 'tasks',
          body: newTask
        });

        // 返回符合Schema的完整对象
        return {
          ...newTask,
          _id: result._id
        };
      } catch (error) {
        console.error('创建任务失败:', error);
        throw new Error('无法创建任务');
      }
    }
  }
};

module.exports = { resolvers };

3. 确认Elasticsearch索引存在

执行以下命令检查tasks索引是否存在:

curl -u elastic:rphcDrBrEm_i1D7_A+hS -k https://localhost:9200/tasks

如果返回404,创建索引:

curl -u elastic:rphcDrBrEm_i1D7_A+hS -k -XPUT https://localhost:9200/tasks

4. 测试请求

确保Postman请求地址为http://localhost:5555/graphql,重新发送创建任务请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 17:37:50