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

GraphQL注册出现Cannot return null for non-nullable field User.createdAt错误如何排查

问题根因分析
  • 字段名映射不匹配:你通过getKnexQuery()调用原生Knex执行插入后,返回的数据库原始字段是蛇形命名的created_at、updated_at,但你的User实体中暴露给GraphQL的字段是驼峰命名的createdAt、updatedAt。原生Knex查询不会触发Mikro-ORM的蛇形/驼峰字段自动转换逻辑,导致返回的user对象中不存在createdAt属性,GraphQL解析非空字段时拿到null就触发报错。
  • 实体实例化流程缺失:你没有走Mikro-ORM的实体创建流程,User类中createdAt = new Date()的默认赋值逻辑不会生效,进一步导致字段无值。
  • 校验逻辑遗漏(次要问题):代码中调用validateRegister拿到校验错误后没有做判断拦截,即使参数不合法也会继续执行后续插入逻辑。
解决方案

方案1:手动映射字段(适合需要保留原生Knex查询的场景)

修改Mutation中try块的代码,将返回的蛇形字段手动转为驼峰:

try{
    const result = await (em as EntityManager).createQueryBuilder(User).getKnexQuery().insert({
        username: options.username,
        password: hashedPassword,
        email: options.email,
        created_at: new Date(),
        updated_at: new Date()
    }).returning("*");
    // 手动映射字段名
    user  = {
        ...result[0],
        createdAt: result[0].created_at,
        updatedAt: result[0].updated_at
    };
}

方案2:改用Mikro-ORM标准API(更推荐,无需手动处理映射)

替换原生Knex插入逻辑,使用Mikro-ORM的create+persistAndFlush方法,自动处理字段映射和默认值赋值:

try{
    user = em.create(User, {
        username: options.username,
        password: hashedPassword,
        email: options.email
    });
    await em.persistAndFlush(user);
}

补充修复项

  1. 补上校验逻辑拦截,避免无效请求:
const errors = validateRegister(options);
// 新增校验拦截
if (errors) {
    return { errors };
}
const hashedPassword = await argon2.hash(options.password)
  1. 统一实体字段的非空约束:你当前User实体中createdAt的@Property装饰器设置了nullable: true,但对应的@Field没有设置nullable,两边约束不一致会埋下隐患,可修改为保持两边一致:
// 要么设为可空
@Field(() => String, { nullable: true })
@Property({type: "date", default: "Now()", nullable: true})
createdAt = new Date();

// 要么设为非空(更推荐,符合业务逻辑)
@Field(() => String)
@Property({type: "date", default: "Now()"})
createdAt = new Date();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:36:04