Make required fields non-nullable in GraphQL schema

When you create a required field, the schema should reflect that and make the corresponding GraphQL field non-nullable.

Using TypeScript with all the fields being nullable forces you to extensively narrow-type everything or use optional chaining where you might not want to.

This could be accompanied by a prompt for migrating old records that do not have a value for a newly added required field.

That said, if you are using DatoCMS for pre-rending (where the data is being retrieved at build time), having an empty required field is out of the question. Therefore, enabling non-nullable fields without a migration prompt is very much possible for these use cases.

@s.verna I can see that you’ve voted for this feature request; would you be able to give us any insight into what blockers (if any) exist to implement this, and if possible, a rough level of effort on your end? I’m just wanting to understand if this would be something that could be implemented relatively quickly and easily if there was demand for it.

For some context on why I’d really love to see this implemented, I am planning to use DatoCMS in combination with https://www.graphql-code-generator.com/ to generate an easy to use and type safe “SDK” for querying data from DatoCMS, and with the current behavior, I can foresee myself needing to add a lot of unnecessary conditional logic to satisfy the TypeScript compiler, for cases that simply cannot happen in reality. This will ultimately mean that it’ll take more effort to build and maintain any DatoCMS consumption code, and extra code will need be shipped to end users, both of which ultimately feels like a waste of resources.

It’s also worth noting that to my understanding, this would be a backwards compatible change to all current DatoCMS GraphQL APIs, as making a nullable field non-nullable would make the new type of a subtype of the old type, meaning that code that is already built to handle the nullable case would still work.

The problem is that, even for required fields, the value of a field might be null.

Counterintuitive, I know.

Suppose you have a model Article, create a bunch of article records, and THEN add a new (required) field.

In this case, the value for such field in already existing records will be null despite the required validation. As a consequence, such records will be marked as invalid (in REST API, the is_valid attribute will be false, and the UI will show a little red dot next to the record).

The only way to provide non-null GraphQL types would be to have different GraphQL queries that only return valid records, while the current allArticles queries return both valid and invalid records.

Of course, if we stop returning invalid records on the current queries, we would generate a non-retrocompatible change.

Hope this clarifies the underlying issue :confused:

@s.verna thanks for the detailed explanation, makes sense!

I guess adding allValidX + validX query patterns likely wouldn’t be viable, because that would introduce the potential for naming collisions (for example, if there were models called both X and validX). Also, doing so would double the amount of queries created per model, which doesn’t feel ideal, although I’m not sure how you could get around this within the same endpoint, as GraphQL (likely rightfully so) doesn’t have the capability to have the schema differ based on query inputs.


Another option I can think of would be to have another GraphQL endpoint exposed (similar to how the “preview” one already exists: API Endpoints - DatoCMS), maybe called something like “strict”, where the records exposed are always valid? I could potentially see this working because:

  1. From my experience, I think that for most use cases, an API user would only need to use queries from one endpoint or the other (and if they did need to access both valid and invalid records so that they could tell the difference between them, it likely wouldn’t be that hard to make calls to the separate endpoints).
  2. The query names could remain the same, meaning that someone could switch from the normal endpoint to the “strict” version without changing their code.

Of course, this is omitting any thought of the additional complexity that this could likely introduce to your codebase.

Correct, a new endpoint was also my conclusion. It’s a bit of work, so we need to book some time for that. We don’t have an ETA yet :roll_eyes:

2 Likes

This would be an amazing addition to Dato. GQL + TS + Codegen is an amazing combination that massively boosts developer productivity but sadly cannot be used to its fullest in the current setup. Have you already had a chance to scope this feature?

2 Likes

Uh, we just went through the effort of enabling strictNullChecks and now I find this. Littering the code with unnecessary null checks makes everything feel fragile. :cold_sweat:


I’ve just pulled our latest code-generated schema and noticed some are now Non-Nullable. @s.verna did you ship and update to this?

@joealden have you found this?

1 Like

Yes, we could change some types to non-nullable even in the current endpoints… but for the complete removal of Maybe types on required fields we still need to build a special endpoint that only returns valid records!

2 Likes

So it’s basically the default fields and array types now? That certainly helps!

I’m not familiar with what’s possible on a GraphQL schema but could there be some way to include info on the type if a field is required or not? This would allow creating a type guard function to check if a record is valid and based on that narrow the type to a modified one in which the required fields have non-null value.

let’s keep in mind the votes on this request: Improve GraphQL Schema for required fields that was a duplicate

Hey everyone, we finally made it! :tada: :tada: :tada:

We just shipped a new X-Exclude-Invalid header that you can add to change the GraphQL types of required fields:

Please let us know what you think!

3 Likes

That is awesome!!! :rocket:

Works like a charm. Great work!

In case this helps others using graphql-codegen, you’ll need to add the header to both a custom fetcher if used and to your codegen.yml schema introspection config:

schema:
  - 'https://graphql.datocms.com':
      headers:
        Authorization: Bearer ********************
        X-Exclude-Invalid: true
4 Likes

You guys rock! :raised_hands:

1 Like