LoginSignup
10
10

More than 1 year has passed since last update.

Typescript + Graphqlを使うならGraphqlはTypeGraphqlがオススメ

Last updated at Posted at 2021-03-07

はじめに

Graphqlは個人的にかなり好きな技術で使い続けていますが、

・Typescriptとの相性があまり良くない
・ORMを使用しているとORMの定義+Graphqlのスキーマ定義もしなければ行けないのでとても面倒
・スキーマ定義、リゾルバー定義、実際のリゾルバーなど関連するファイルが多いので、変更する際は複数のファイルを変更する必要があり、面倒

など、不満点も散見されました。

そんな中TypeGraphqlというものに出会い、これならば上記の不満点も解消しつつ効率的な開発ができると思いましたので、共有します。

TypeGraphql公式ドキュメント

記事更新履歴

日付 更新内容
2021/05/31 「4・バリデーションが簡単に実装できる」を追加

TypeGraphqlのオススメな点

1.デコレータを使った直感的な操作

・スキーマ定義

TypeGraphqlはTypescript/Javascriptの機能の一つであるクラスとデコレータ(@をつけて定義する宣言)と用いてスキーマの定義
が可能になります。

従来Graphqlでスキーマ定義をする際はSDL(スキーマ定義言語)を用いて定義しました。

// 例:「User」というスキーマを定義
import { gql } from 'apollo-server'

const typeDefs = gql`
type User {
    id: ID!
    userName: String!
    password: String!
}
`

TypeGraphqlでスキーマ定義するとこうなります。

// 例: TypeGraphqlを用いて「User」スキーマを定義
import { Field, ObjectType } from 'type-graphql'

@ObjectType()
class User {

    @Field(() => ID)
    id: number;

    @Field(() => String)
    userName: string;

    @Field(() => String)
    password: string;

}

これだけだと大して楽になっていないように感じるのですが、後述するORMとの組み合わせで、この記述方法は真価を発揮します。

・リゾルバー定義

従来リゾルバーを定義する際はまずリゾルバーの型を定義して、実際の処理を記述しました。

import { gql } from 'apollo-server'

const typeDefs = gql`
   type User {
       id: ID!
       userName: String!
       password: String!
   }

// リゾルバーの型定義
   type Query {
       getUsers: [User]!
       getUser(userName: String!): User!
   }
`
// リゾルバーの実装
   const resolvers = {
       Query: {
           //全ユーザーを取得
           getUsers() => {
              //省略 
           }
           //引数(userName)のユーザーを取得
           getUser(_, {userName}) => {
             //省略
           }      
       }
   }

このように型定義と実装を分けて記述しなければならず、変更をする際も型定義を変えてから実装も変えて・・・というようにファイルを行ったり来たりすることが多く、当然ミスも多くなります。
私もこのやり方で開発していた際は、リゾルバーを変更した時にどちらかを変更するのを忘れてエラーを起こしてしまうことが度々ありました。

ではtypeGraphqlでリゾルバーを定義するとどうなるでしょう。

import { Args, Query, Resolver } from 'type-graphql'

@Resolver()
  class resolvers {
      //全ユーザーを取得
      @Query(() => User)
      getUsers() {
         //省略
      }
      //引数(userName)のユーザーを取得
      @Query(() => User)
      getUser(@Args('userName') userName: string) {
         //省略
      }
  }

どうでしょうか。デコレータによってリゾルバーの型定義と実装を一つにまとめることが可能になったことがわかります。
一つにまとめることで変更する際は変更するファイルが一つで済むのと、Typescriptによってエラー箇所も事前に知らせてくれることがほとんどなことから、ミスも格段に減り、開発効率が良くなります。

2.ORMと組み合わせることで、スキーマ定義が超簡単にできる

TypeGraphqlはORMとの相性も抜群で、Sequalize-typescript、TypeORM、TypeGooseなどのデコレータでエンティティを定義するORMライブラリを使用すれば、ORMのエンティティにTypeGraphqlのデコレータを加えるだけで、Graphqlのスキーマ定義ができてしまいます。

例として、TypeORMで定義したエンティティにTypeGraphqlのスキーマを定義してみましょう。

import { Field, ObjectType } from "type-graphql";
import { Column, Entity } from "typeorm";

@ObjectType()//TypeGraphqlの定義部分
@Entity('users')
export class User extends Entity {

    @Field(() => ID)//TypeGraphqlの定義部分
    @PrimaryGeneratedColumn()
    id: number;

    @Field(() => String)//TypeGraphqlの定義部分
    @Column()
    userName: string;

    @Field(() => String)//TypeGraphqlの定義部分
    @Column()
    password: string;

TypeORMのエンティティに少しデコレータをつけるだけで簡単にGraphqlのスキーマ定義ができました!
Graphql + データベースの定義にORMを使用する場合だと、従来はORMのエンティティも定義して、別ファイルでGraphqlのスキーマ定義もしなければならず、変更の際はどちらも変える・・・といった具合に非常にミスも起こりやすく面倒な状況だったのですが、TypeGraphqlにするとORMのエンティティの定義とGraphqlのスキーマ定義を一つのファイルで管理することができるため非常に便利です。

TypeORM公式ドキュメント

3.Middlewareが簡単に実装できる

TypeGraphqlはMiddlewareの実装も簡単に行うことが可能です。
従来だと「Graphql-middleware」のようなパッケージを使用したりする場合が一般的かと思いますが、TypeGraphqlにはMiddlewareを実装するための関数やデコレータが用意されていて、簡単に実装できるようになっています。

import { MiddlewareFn } from "type-graphql";

export const SampleMiddleware: MiddlewareFn = () => {
  //省略
}

定義したMiddlewareはリゾルバーに「@UseMiddleware()」を宣言するだけで簡単に使用できます。

import { Args, Query, Resolver, UseMiddleware } from 'type-graphql'

@Resolver()
  class resolvers {
      @Query(() => User)
      // ↓デコレータをつけるだけ!
      @UseMiddleware(SampleMiddleware)
      getUsers() {
         //省略
      }
  }

簡単にMiddlewareが実装できました!

4・バリデーションが簡単に実装できる

TypeGraphqlをインストールする時にバリデーションライブラリである「class-validator」を同時にインストールする必要があるのですが、このライブラリをTypeGraphqlで定義するInputTypeの項目に付け加えるだけで簡単に入力項目のバリデーションが可能になります。

CreateUserInput.ts

// 例: class-validatorを用いてInputFieldのバリデーションを行う
import { Field, InputType } from 'type-graphql';
import { IsEmail, IsNotEmpty, MaxLength, MinLength } from 'class-validator';

@InputType()
export class CreateUserInput {

    @Field()
    @IsNotEmpty({ message: 'ユーザー名は必須項目です!' })
    @MaxLength(15, { message: '15文字以内に収めて下さい!' })
    userName: string;

    @Field()
    @IsNotEmpty({ message: 'Eメールは必須項目です!' })
    @IsEmail({}, { message: '無効なEメールの形式です!' })
    email: string;

    @Field()
    @IsNotEmpty({ message: 'パスワードは必須項目です!' })
    @MaxLength(15, { message: '15文字以内に収めて下さい!' })
    @MinLength(2, { message: '2文字以上に設定して下さい!' })
    password: string;

    @Field()
    @IsNotEmpty({ message: '確認用パスワードは必須項目です!' })
    @MaxLength(15, { message: '15文字以内に収めて下さい!' })
    confirmPassword: string;

}

例ではミューテーションを定義する際に作成しておくと便利な@InputTypeclass-validatorのデコレータを付与してバリデーションを行っています。
通常バリデーションはリゾルバー内に定義することが一般的で、そうするとリゾルバー内がGraphqlの処理とバリデーションとでごちゃごちゃになりコードの見通しが悪くなりますが、TypeGraphqlならば上記のように@InputType内にバリデーションを簡単に実装できる上にリゾルバー内のコードからはバリデーションのコードが除かれるため、見通しの良いコードになります。
class-validatorで使用できるデコレータは↓を参考にして下さい。
class-validator公式
なお、class-validatorは独自のデコレータを作成することもできます。
記事作成中・・・

まとめ

昨今はJSに代わりTSを使用することが主流になりつつあるように感じますが、GraphqlもTypeGraphqlを使用するほうが個人的には効率良く開発ができているように感じます。
ぜひ一度、Typescript + TypeGraphqlでバックエンドを実装して見て下さい。

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10