GraphQL で配列が nullable になって扱いにくすぎる件を何とかする

はじめに

GraphQL のスキーマに配列が含まれると GraphQL Code Generator で nullable になってしまい、フロントエンドで扱いにくい問題の対処方法を解説します。

背景

REST しかやったことない人間が GraphQL のスキーマを見よう見まねで書いてみました。
type BlogPost {
  slug: string!
}

query Query {
  allBlogPost: [BlogPost]
}
このスキーマを GraphQL Code Generator で型生成するとこのような型が生成されます。
export type Maybe<T> = T | null;

export type Query = {
  __typename?: "Query";
  allBlogPost?: Maybe<Array<Maybe<BlogPost>>>;
};
allBlogPostBlogPost の配列 (空配列を含む) のイメージだったのですが、実際は下記の 5 パターンが返ってくる可能性のあるスキーマになってしまいました。パターンが増えるので考えることが増えてしまい、開発効率が低下してしまいます。(プログラムが牢固になるという利点はありますが...)
意外に思ったのが、配列の中に null が入るパターンです。 GraphQL スキーマではエクステンションマークを明示的に付けない場合は nullable になるのでこのような型になります。
  • {}
  • {allBlogPost: null}
  • {allBlogPost: []}
  • {allBlogPost: [null]}
  • {allBlogPost: [{slug: "slug"}]}
これで slug 一覧を生成しようとおもったらこのような感じでしょうか..。
data?.allBlogPost
  ?.filter((v): v is NonNullable<typeof v> => v !== null)
  .map((v) => v.slug) ?? [];

null を許容しないスキーマに変更する

GraphQL のドキュメントを参考に、エクステンションマークを付けて non-nullable にしてみました。配列の内外共にエクステンションマークを付けるようにします。
Schemas and Types | GraphQL
https://graphql.org/learn/schema/
query Query {
  allBlogPost: [BlogPost!]!
}
このスキーマを GraphQL Code Generator で型生成するとこのような型が生成されます。
export type Query = {
  __typename?: "Query";
  allBlogPost: <Array<BlogPost>>;
};
パターンが絞られ、必ず配列が返るようになります。
  • {allBlogPost: []}
  • {allBlogPost: [{slug: "slug"}]}
これならシンプルに実装できます。
data.allBlogPost.map((v) => v.slug);
バックエンド側でも null を返さないよう実装するように仕様が明確化されます。

まとめ

明示的に non-nullable を指示することで、実装をシンプルにできました。