Search code examples
graphqlapollotypegraphql

After switchinng to graphql 15.5.1 returning an empty array results in a "cannot return null value" error


EDIT: You can skip to the end for a simplified example of the error in codesandbox

I'm upgrading my apollo project to the new version of graphql and I'm getting a weird error. Can't find any results that match this scenario on google. When I attempt to return an empty array of ObjectTypes DrivingActivitiesDailySummaryType that have non-nullable fields (including day), it throws a "Cannot return null for non-nullable field DrivingActivitiesDailySummaryType.day.". This worked as intended in version 14.7.0. What's happening here? How to avoid it? I would think that the normal behavior would be to allow empty arrays and I can't find anything in the changelog that would indicate a change of this type.

My resolver currently just returns a hardcoded value for testing:

@Query(() => DrivingActivityPayload)
    public async totalDrivingActivity(
        @Ctx("user") userContext: UserContext,
        @Args() input: DrivingActivitiesQueryInput
    ): Promise<DrivingActivityPayload> {
        return {
            totalSummary: { speed: 1, activities: 1, duration: 1, distance: 1 },
            dailySummaries: [],
            monthlySummaries: [],
        }
    }

My payload deffinition:

@ObjectType()
export class DrivingActivityPayload {
    @Field(() => DrivingActivitiesTotalSummaryType)
    public totalSummary: DrivingActivitiesTotalSummaryType

    @Field(() => DrivingActivitiesDailySummaryType)
    public dailySummaries: DrivingActivitiesDailySummaryType[]

    @Field(() => DrivingActivitiesMonthlySummaryType)
    public monthlySummaries: DrivingActivitiesMonthlySummaryType[]

    constructor(
        totalSummary: DrivingActivitiesTotalSummaryType,
        dailySummaries: DrivingActivitiesDailySummaryType[],
        monthlySummaries: DrivingActivitiesMonthlySummaryType[]
    ) {
        this.totalSummary = new DrivingActivitiesTotalSummaryType(totalSummary)
        this.dailySummaries = dailySummaries.map(summary => new DrivingActivitiesDailySummaryType(summary))
        this.monthlySummaries = monthlySummaries.map(summary => new DrivingActivitiesMonthlySummaryType(summary))
    }
}

The GraphQL error:

  "errors": [
    {
      "message": "Cannot return null for non-nullable field DrivingActivitiesDailySummaryType.day.",
      "locations": [
        {
          "line": 18,
          "column": 7
        }
      ],
      "path": [
        "totalDrivingActivity",
        "dailySummaries",
        "day"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "stacktrace": [
            "Error: Cannot return null for non-nullable field DrivingActivitiesDailySummaryType.day.",
            "    at completeValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:559:13)",
            "    at resolveField ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:472:19)",
            "    at executeFields ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:292:18)",
            "    at collectAndExecuteSubfields ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:748:10)",
            "    at completeObjectValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:738:10)",
            "    at completeValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:590:12)",
            "    at completeValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:556:21)",
            "    at resolveField ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:472:19)",
            "    at executeFields ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:292:18)",
            "    at collectAndExecuteSubfields ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:748:10)",
            "    at completeObjectValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:738:10)",
            "    at completeValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:590:12)",
            "    at completeValue ([PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:556:21)",
            "    at [PROJECT-FOLDER]/node_modules/graphql/execution/execute.js:469:16",
            "    at processTicksAndRejections (node:internal/process/task_queues:96:5)",
            "    at async Promise.all (index 0)"
          ]
        }
      }
    }
  ],
  "data": null
}

The DrivingActivitiesDailySummaryType deffinition (though this shouldn't matter):

@ObjectType()
export class DrivingActivitiesDailySummaryType {
    @Field(() => Date)
    public day: Date

    @Field()
    public distance: number

    @Field()
    public activities: number

    constructor(summary: DrivingActivitiesDailySummaryType) {
        this.day = summary.day
        this.activities = summary.activities
        this.distance = summary.distance
    }
}

Edit: Here's a sandbox link with the reproduced error: https://codesandbox.io/s/heuristic-jepsen-5frr3?file=/package.json (I didn't know how to change the default terminal behaviour so you'll have to open a new terminal with the "+" icon and run "npm start") When using the provided package.json (with new versions) the error is reproduced, when you switch the contents of package.json with package-old.json (older versions of graphql and type-graphql) there is no error.

EDIT2: You will probably need to add "/graphql" to the codesandbox link for it to work


Solution

  • You should define daily and monthly summaries as array:

    @Field(() => [DrivingActivitiesDailySummaryType])
    public dailySummaries: DrivingActivitiesDailySummaryType[]
    
    @Field(() => [DrivingActivitiesMonthlySummaryType])
    public monthlySummaries: DrivingActivitiesMonthlySummaryType[]