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

Knex带.where()的查询在GraphQL中返回null求助

Knex + Apollo Server带where条件查询返回null问题排查

问题概述

用Knex和Apollo Server搭建API时,不带where条件的getUsers查询能正常返回所有用户,但带where条件的getUser查询(无论用id、firstName还是email等字段)始终返回null,服务运行正常,SQL日志显示查询语句格式正确。

相关代码片段

服务端入口代码

import { ApolloServer } from 'apollo-server'
import knex from './knex'
import { resolvers, typeDefs } from './schema'
import { UserSQLDataSource } from './schema/users/datasource'

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources: () => {
    return {
      userDb: new UserSQLDataSource(knex),
    }
  },
  uploads: false,
  cors: {
    origin: ['https://studio.apollographql.com'],
    credentials: true,
  },
})

server.listen(4003).then(({ url }) => {
  console.log(`Server listening on url ${url}`)
})

用户数据源实现(含问题代码)

import { SQLDatasource } from '../../schema/datasources'

export class UserSQLDataSource extends SQLDatasource {
  constructor(knex) {
    super(knex, 'users')
  }

  async getUsers() {
    return await this.db(this.tableName)
  }

  async getUser(id) {
    try {
      const query = this.db(this.tableName).where({ id: id })
      console.log(query.toSQL())

      return await query
    } catch (error) {
      console.error(error)
      throw error
    }
  }
}

GraphQL用户类型定义

import { gql } from 'apollo-server'

export const userTypeDefs = gql`
  extend type Query {
    user(id: ID!): User!
    users: [User!]!
  }

  type User {
    id: ID!
    firstName: String!
    lastName: String!
    userName: String!
    email: String!
    password_hash: String!
    token: String!
    createdAt: String!
    updatedAt: String!
  }
`

用户Resolver代码

const users = async (_, { input }, { dataSources }) => {
  const users = await dataSources.userDb.getUsers(input)
  return users
}

const user = async (_, { id }, { dataSources }) => {
  const user = await dataSources.userDb.getUser(id)
  return user
}

export const userResolvers = {
  Query: { user, users },
}

查询日志输出

Server listening on url http://localhost:4003/
{
  method: 'select',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [ '1' ],
  __knexQueryUid: 'sH05Z4l3pgdNm5mY9YPkeu6',
  sql: 'select * from `users` where `id` = ?'
}

问题原因分析

  1. 返回值类型不匹配:Knex的where()查询默认返回数组(即使只匹配一条数据),但GraphQL的user查询定义返回的是单个User!对象。当Resolver返回数组时,Apollo无法将数组映射到单个User类型,最终返回null。
  2. 非空类型约束:GraphQL定义user: User!为非空类型,若查询结果为空数组,直接返回会触发类型不匹配错误,表现为返回null。

解决方案

1. 修改数据源查询逻辑,返回单个对象

在getUser方法中,使用Knex的first()方法直接获取单个结果(替代返回数组):

async getUser(id) {
  try {
    // 使用first()直接获取匹配的第一条数据,返回单个对象而非数组
    const user = await this.db(this.tableName).where({ id }).first();
    
    // 若未找到用户,抛出明确错误(符合GraphQL非空类型约束)
    if (!user) {
      throw new Error(`User with id ${id} not found`);
    }
    
    return user;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

2. 验证数据存在性

确认数据库中存在对应id的用户记录,可直接执行日志中的SQL语句(如select * from users where id = 1)验证是否有返回结果。

3. 可选:处理ID类型转换(若数据库ID为数字)

GraphQL的ID类型默认传递为字符串,若数据库中id为数字类型,可显式转换确保匹配:

// 将字符串ID转为数字
const user = await this.db(this.tableName).where({ id: parseInt(id, 10) }).first();

验证修改

修改后重新启动服务,执行user(id: "1")查询,应正常返回单个用户对象;若未找到用户,会抛出明确的错误信息而非返回null。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 16:25:39