GraphQL + Typoskript = Liebe. TypeGraphQL v1.0





TypeGraphQL v1.0



Am 19. August wurde das TypeGraphQL-Framework veröffentlicht, das die Arbeit mit GraphQL in Typescript vereinfacht. Seit zweieinhalb Jahren hat das Projekt eine solide Community und Unterstützung von mehreren Unternehmen erhalten und gewinnt zuversichtlich an Popularität. Nach über 650 Commits hat er über 5000 Sterne und 400 Gabeln auf Github, die Frucht der harten Arbeit des polnischen Entwicklers Michal Litek. In Version 1.0 verbesserte sich die Leistung erheblich, Schemas wurden isoliert und die vorherige Redundanz beseitigt. Zwei Hauptfunktionen wurden angezeigt: Direktiven und Erweiterungen. Das Framework wurde auf volle Kompatibilität mit GraphQL gebracht.



Wofür ist dieser Rahmen?



Michal bezeichnet den Entwicklungsprozess aufgrund seiner Redundanz und Komplexität bei der Änderung des vorhandenen Codes als "schmerzhaft".



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




Es klingt nicht sehr praktisch, und bei diesem Ansatz ist das Hauptproblem die Redundanz des Codes, die es schwierig macht, alle Parameter beim Schreiben zu synchronisieren, und beim Ändern Risiken hinzufügt. Um unserer Entität ein neues Feld hinzuzufügen, müssen wir alle Dateien durchlaufen: Ändern Sie die Entitätsklasse und dann den Schemateil und die Schnittstelle. Dies gilt auch für Eingabedaten oder Argumente. Es ist leicht zu vergessen, ein Element zu aktualisieren oder einen Fehler in einem Typ zu machen.



Um Redundanz zu bekämpfen und all diese manuelle Arbeit zu automatisieren, wurde TypeGraphQL erstellt. Es basiert auf der Idee, alle Informationen an einem Ort zu speichern und das Datenschema durch Klassen und Dekoratoren zu beschreiben. Das Framework übernimmt auch die manuelle Arbeit der Abhängigkeitsinjektion, Datenvalidierung und -autorisierung und entlädt den Entwickler.



Arbeitsprinzip



Schauen wir uns an, wie TypeGraphQL am Beispiel der GraphQL-API für die Rezeptdatenbank funktioniert.



So sieht das Schema in SDL aus:



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




Schreiben wir es als Rezeptklasse um:



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




Rüsten wir die Klasse und die Eigenschaften mit Dekorateuren aus:



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

        @Field()
        title: string;

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

        @Field()
        creationDate: Date;

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




Detaillierte Regeln zur Beschreibung von Feldern und Typen im entsprechenden Abschnitt der Dokumentation



Anschließend werden die üblichen CRUD-Abfragen und -Mutationen beschrieben. Erstellen Sie dazu einen RecipeResolver-Controller mit dem an den Konstruktor übergebenen RecipeService:



    @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;
            }
        }
    }




Hier wird der Dekorator @Authorized () verwendet, um den Zugriff auf nicht autorisierte (oder nicht ausreichend privilegierte) Benutzer zu beschränken. Weitere Informationen zur Autorisierung finden Sie in der Dokumentation .



Es ist Zeit, NewRecipeInput und RecipesArgs hinzuzufügen:



	@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;
	}




Länge, Mindestund @ArrayMaxSize sind Dekoratoren aus der Validator-Klasse, die die Feldvalidierung automatisch durchführen.



Der letzte Schritt ist das Zusammenbauen der Schaltung. Dies erfolgt durch die buildSchema-Funktion:



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




Und alle! Wir haben jetzt ein voll funktionsfähiges GraphQL-Schema. In kompilierter Form sieht es so aus:



	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!
	}




Dies ist ein Beispiel für grundlegende Funktionen. TypeGraphQL kann eine Reihe anderer Tools aus dem TS-Arsenal verwenden. Sie haben bereits Links zur Dokumentation gesehen :)



Was ist neu in 1.0



Werfen wir einen kurzen Blick auf die wichtigsten Änderungen in der Version:



Performance



TypeGraphQL ist im Wesentlichen eine zusätzliche Abstraktionsebene über der graphql-js-Bibliothek und wird immer langsamer ausgeführt. Im Vergleich zu Version 0.17 erhöht das Framework bei einer Stichprobe von 25.000 verschachtelten Objekten den Aufwand um das 30-fache - von 500% auf 17% mit der Möglichkeit einer Beschleunigung auf bis zu 13%. Einige nicht triviale Optimierungsmethoden sind in der Dokumentation beschrieben .



Stromkreise trennen



In älteren Versionen wurde das Schema aus allen Metadaten erstellt, die von Dekorateuren erhalten wurden. Jeder nachfolgende Aufruf von buildSchema gab dasselbe Schema zurück, das aus allen im Store verfügbaren Metadaten erstellt wurde. Jetzt sind die Schemas isoliert und buildSchema gibt nur die Anforderungen aus, die in direktem Zusammenhang mit den angegebenen Parametern stehen. Das heißt, indem nur der Resolver-Parameter geändert wird, werden verschiedene Operationen an GraphQL-Schemas ausgeführt.



Richtlinien und Erweiterungen



Es gibt zwei Möglichkeiten, Schemaelementen Metadaten hinzuzufügen: GraphQL-Anweisungen sind Teil der SDL und können direkt im Schema deklariert werden. Sie können es auch ändern und bestimmte Vorgänge ausführen, z. B. einen Verbindungstyp für die Paginierung generieren. Sie werden mit den Dekoratoren @Directive und @Extensions angewendet und unterscheiden sich in ihrer Herangehensweise an die Schemakonstruktion. Dokumentationsanweisungen , die Erweiterungsdokumentation .



Konverter und Argumente für Schnittstellenfelder



Hier lag die letzte Grenze der vollständigen Kompatibilität mit GraphQL. Sie können jetzt Konverter für Schnittstellenfelder mithilfe der @ ObjectType-Syntax definieren:



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




Einige Ausnahmen werden hier beschrieben .



Konvertieren verschachtelter Eingaben und Arrays



In früheren Versionen wurde eine Instanz der Eingabeklasse nur auf der ersten Verschachtelungsebene erstellt. Dies führte zu Problemen und Fehlern bei der Validierung. Fest.



Fazit



Während der gesamten Entwicklungsphase blieb das Projekt offen für Ideen und Kritik, Open Source und Brand. 99% des Codes wurde von Michal Litek selbst geschrieben, aber die Community hat auch einen großen Beitrag zur Entwicklung von TypeGraphQL geleistet. Mit zunehmender Popularität und finanzieller Unterstützung kann es nun zu einem echten Standard auf seinem Gebiet werden. Michals Github Doki Twitter-



Website












All Articles