GraphQL + Typescript =愛。TypeGraphQL v1.0





TypeGraphQL v1.0



8月19日に、TypeGraphQLフレームワークがリリースされました。これにより、TypescriptでのGraphQLの操作が簡素化されます。このプロジェクトは2年半の間、いくつかの企業から強固なコミュニティとサポートを獲得し、自信を持って人気を集めています。650を超えるコミットの後、彼はgithubに5,000を超えるスターと400のフォークを持っています。これは、ポーランドの開発者MichalLitekの努力の成果です。バージョン1.0では、パフォーマンスが大幅に向上し、スキーマが分離され、以前の冗長性が取り除かれました。ディレクティブと拡張機能の2つの主要な機能が登場し、フレームワークはGraphQLと完全に互換性があります。



このフレームワークは何のためのものですか?



Michalは、裸のGraphQLでの経験を参照して、既存のコードを変更する冗長性と複雑さのために、開発プロセスを「苦痛」と呼んでいます。



  1. GraphQL SDL (Schema Definition Language);
  2. ORM (Object-Relational Mapping), ;
  3. , , ...
  4. , .
  5. , , .




あまり実用的ではないようですが、このアプローチでは、主な問題はコードの冗長性です。これにより、コードを作成するときにすべてのパラメーターを同期することが難しくなり、変更するときにリスクが追加されます。エンティティに新しいフィールドを追加するには、すべてのファイルを反復処理する必要があります。エンティティクラスを変更してから、スキーマ部分とインターフェイスを変更します。入力データや引数についても同じです。1つの要素を更新するのを忘れたり、1つのタイプを間違えたりするのは簡単です。



冗長性に対抗し、このすべての手作業を自動化するために、TypeGraphQLが作成されました。これは、すべての情報を1つの場所に保存し、クラスとデコレータを介してデータスキーマを記述するという考えに基づいています。フレームワークは、依存関係の挿入、データの検証と承認、開発者のアンロードの手動作業も引き受けます。



動作原理



例として、レシピデータベースのGraphQLAPIを使用してTypeGraphQLがどのように機能するかを見てみましょう。



これは、SDLでのスキーマの外観です。



    type Recipe {
        id: ID!
        title: String!
        description: String
        creationDate: Date!
        ingredients: [String!]!
    }




Recipeクラスとして書き直してみましょう。



    class Recipe {
        id: string;
        title: string;
        description?: string;
        creationDate: Date;
        ingredients: string[];
    }




クラスとプロパティにデコレータを装備しましょう。



    @ObjectType()
    class Recipe {
        @Field(type => ID)
        id: string;

        @Field()
        title: string;

        @Field({ nullable: true })
        description?: string;

        @Field()
        creationDate: Date;

        @Field(type => [String])
        ingredients: string[];
    }




ドキュメントの対応するセクションで フィールドとタイプを説明するための詳細なルール次に



、通常のCRUDクエリとミューテーションについて説明します。これを行うには、RecipeServiceをコンストラクターに渡してRecipeResolverコントローラーを作成しましょう。



    @Resolver(Recipe)
    class RecipeResolver {
        constructor(private recipeService: RecipeService) {}

        @Query(returns => Recipe)
        async recipe(@Arg("id") id: string) {
            const recipe = await this.recipeService.findById(id);
            if (recipe === undefined) {
                throw new RecipeNotFoundError(id);
            }
            return recipe;
        }

        @Query(returns => [Recipe])
        recipes(@Args() { skip, take }: RecipesArgs) {
            return this.recipeService.findAll({ skip, take });
        }

        @Mutation(returns => Recipe)
        @Authorized()
        addRecipe(
            @Arg("newRecipeData") newRecipeData: NewRecipeInput,
            @Ctx("user") user: User,
        ): Promise<Recipe> {
            return this.recipeService.addNew({ data: newRecipeData, user });
        }

        @Mutation(returns => Boolean)
        @Authorized(Roles.Admin)
        async removeRecipe(@Arg("id") id: string) {
            try {
                await this.recipeService.removeById(id);
                return true;
            } catch {
                return false;
            }
        }
    }




ここで、@ Authorized()デコレータは、許可されていない(または特権が不十分な)ユーザーへのアクセスを制限するために使用されます。承認について詳しくは、ドキュメントをご覧ください



NewRecipeInputとRecipesArgsを追加する時が来ました:



	@InputType()
	class NewRecipeInput {
		@Field()
		@MaxLength(30)
		title: string;

		@Field({ nullable: true })
		@Length(30, 255)
		description?: string;

		@Field(type => [String])
		@ArrayMaxSize(30)
		ingredients: string[];
	}

	@ArgsType()
	class RecipesArgs {
		@Field(type => Int, { nullable: true })
		@Min(0)
		skip: number = 0;

		@Field(type => Int, { nullable: true })
		@Min(1) @Max(50)
		take: number = 25;
	}




長さ最小および@ArrayMaxSizeは、フィールド検証を自動的に行うバリデータークラスのデコレーターです。



最後のステップは、実際に回路を組み立てることです。これは、buildSchema関数によって実行されます。



    const schema = await buildSchema({
        resolvers: [RecipeResolver]
    });




以上です!これで、完全に機能するGraphQLスキーマができました。コンパイルされた形式では、次のようになります。



	type Recipe {
		id: ID!
		title: String!
		description: String
		creationDate: Date!
		ingredients: [String!]!
	}
	input NewRecipeInput {
		title: String!
		description: String
		ingredients: [String!]!
	}
	type Query {
		recipe(id: ID!): Recipe
		recipes(skip: Int, take: Int): [Recipe!]!
	}
	type Mutation {
		addRecipe(newRecipeData: NewRecipeInput!): Recipe!
		removeRecipe(id: ID!): Boolean!
	}




これは基本的な機能の例です。実際、TypeGraphQLはTSアーセナルの他のツールを多数使用できます。あなたはすでにドキュメントへのリンクを見てきました:)



1.0の新機能



リリースの主な変更点を簡単に見てみましょう。



パフォーマンス



TypeGraphQLは、基本的にgraphql-jsライブラリの抽象化の追加レイヤーであり、常にそれよりも実行速度が遅くなります。しかし現在、バージョン0.17と比較して、25,000のネストされたオブジェクトのサンプルでは、​​フレームワークは30分の1のオーバーヘッドを追加します-500%から17%に、最大13%まで加速する可能性があります。いくつかの重要な最適化方法は、ドキュメントに記載されています



絶縁回路



古いバージョンでは、スキーマはデコレータから取得したすべてのメタデータから構築されていました。その後のbuildSchemaの呼び出しごとに、ストアで使用可能なすべてのメタデータから構築された同じスキーマが返されました。これで、スキームが分離され、buildSchemaは、指定されたパラメーターに直接関連する要求のみを発行します。つまり、resolversパラメーターのみを変更することにより、GraphQLスキーマに対してさまざまな操作を実行します。



ディレクティブと拡張機能



スキーマ要素にメタデータを追加するには、2つの方法があります。GraphQLディレクティブはSDLの一部であり、スキーマで直接宣言できます。また、変更して特定の操作を実行することもできます。たとえば、ページネーション用の接続タイプを生成します。これらは@Directiveおよび@Extensionsデコレータを使用して適用され、スキーマ構築へのアプローチが異なります。ドキュメントディレクティブ拡張機能のドキュメント



インターフェイスフィールドのコンバータと引数



GraphQLとの完全な互換性の最後のフロンティアはここにあります。@ObjectType構文を使用して、インターフェイスフィールドのコンバータを定義できるようになりました。



    @InterfaceType()
    abstract class IPerson {
        @Field()
        avatar(@Arg("size") size: number): string {
            return `http://i.pravatar.cc/${size}`;
        }
    }




ここで は、いくつかの例外について説明します



ネストされた入力と配列の変換



以前のバージョンでは、入力クラスのインスタンスは最初のネストレベルでのみ作成されていました。これにより、検証に問題とバグが生じました。修繕。



結論



開発期間全体を通して、プロジェクトはアイデアや批判、オープンソース、そして熱狂的なものに対してオープンなままでした。コードの99%はMichal Litek自身によって作成されましたが、コミュニティもTypeGraphQLの開発に多大な貢献をしました。現在、人気と財政的支援の増加に伴い、それはその分野の真の標準になることができます。ミハエルのGithubのドキTwitterの



ウェブサイト












All Articles