📈

Apollo Server + Nexus + PrismaでGraphQL開発: InputObjectとバリデーション

2021/12/06に公開

この記事は、いかずちさんだー Advent Calendar 6日目の記事です。

趣旨

前回の記事では、NexusでRelayのページングを実装するにはどうしたらいいかを解説しました。
今回の記事では、InputObjectとバリデーションを実装するための方法を説明します。
とはいえ、バリデーションについてはこれがベストなのかはかなり疑問があります…。

InputObject

GraphQLのMutationでは、引数をInputObjectとしてひとつのオブジェクトにまとめてしまうのが一般的です。
どういう理由なのか明確に把握しているわけではないのですが、引数が多くなりすぎるのを防ぐためと考えればよいのかなと思っています。
昔のRelayの仕様ではInputObjectのフィールドとしてclientMutationIdというのを持たせて、サーバ側はそれをそのまま返すというのをやっていたみたいですが、現代的には不要そうです。

実装

以下のように、引数を外出ししてやります。
そんなに難しいことはないですが、フィールド単位でデフォルト値を設定できなくなることには留意しておきましょう。
まあ、対策方法とかはないですが…。

src/mutation/user.ts
export const createUser = mutationField('createUser', {
  type: 'User',
  args: {
-   name: nonNull(arg({ type: 'string' })),
-   email: nonNull(arg({ type: 'string' })),
+   input: nonNull(arg({ type: 'CreateUserInput' })),
  },
- resolve(_parent, { name, email }, ctx) {
+ resolve(_parent, { input }, ctx) {
-   return ctx.prisma.user.create({ data: { name, email }})
+   return ctx.prisma.user.create({ data: { ...input }})
  },
})

export const createUserInput = inputObjectType({
  name: 'CreateUserInput',
  definition(t) {
    t.nonNull.string('name')
    t.nonNUll.string('email')
  },
})

nexus-validate

Nexusでバリデーションを実装する方法はいろいろありますが、今回は、nexus-validateを使っていこうと思います。
これを用いることで、Mutationの定義の際にvalidate関数を実装できるようになります。

導入

npm i nexus-validate yupとして導入したのちに、src/schema.tsに設定を追加します。

src/schema.ts
const schema = makeSchema({
  ...
  plugins: [
    ...
    validatePlugin()
  ],
})

実装

以前に定義したcreateUser mutationにバリデーションを追加しましょう。

src/mutation/user.ts
export const createUser = mutationField('createUser', {
  type: 'User',
  args: {
    input: nonNull(arg({ type: 'CreateUserInput' })),
  },
+ validate: ({ string }, { input }, ctx) => {
+   string().required('name is required').min(3, 'name is too short').validate(input.name)
+   string().required('email is required').email('email is invalid').validate(input.email)
+ },
  resolve(_parent, { name, email }, ctx) {
    return ctx.prisma.user.create({ data: { name, email }})
  },
})

これで、名前は3文字以上、メールアドレスは有効なメールアドレスであるという条件のバリデーションを追加できました。
今回はDBを参照していないのでcontextを使っていませんが、必要に応じてvalidate関数内からcontextを利用することができます。
また、今回はnexus-validateの機能でしかバリデーション処理をしていませんが、

src/mutation/user.ts
    if(input.name.length < 3) throw new UserInputError('name is too short', { invalidArgs: ['name'] })

などと、より柔軟にバリデーションを記述することもできます。

おわりに

今回は、InputObjectとバリデーションの追加によって、Mutationの肉付けを行いました。
次回は、認証・認可周りについて説明していこうと思います。

GitHubで編集を提案

Discussion