TypeGraphQL v1.0
Le 19 août, le framework TypeGraphQL a été publié, ce qui simplifie le travail avec GraphQL dans Typescript. Depuis deux ans et demi, le projet a acquis une solide communauté et le soutien de plusieurs entreprises et gagne en popularité avec confiance. Après plus de 650 engagements, il compte plus de 5 000 étoiles et 400 fourchettes sur github, fruit du travail acharné du développeur polonais Michal Litek. Dans la version 1.0, les performances se sont considérablement améliorées, les schémas ont été isolés et ont éliminé la redondance précédente, deux fonctionnalités majeures sont apparues - les directives et les extensions, le framework a été amené à une compatibilité totale avec GraphQL.
À quoi sert ce cadre?
Michal, se référant à son expérience avec le nu GraphQL, qualifie le processus de développement de «douloureux» en raison de la redondance et de la complexité de la modification du code existant:
Cela ne semble pas très pratique, et avec cette approche, le principal problème est la redondance du code, ce qui rend difficile la synchronisation de tous les paramètres lors de son écriture et ajoute des risques lors du changement. Pour ajouter un nouveau champ à notre entité, nous devons parcourir tous les fichiers: changer la classe d'entité, puis changer la partie de schéma et l'interface. C'est la même chose avec les données d'entrée ou les arguments, il est facile d'oublier de mettre à jour un élément ou de faire une erreur dans un type.
Pour lutter contre la redondance et automatiser tout ce travail manuel, TypeGraphQL a été créé. Il est basé sur l'idée de stocker toutes les informations en un seul endroit, décrivant le schéma de données à travers des classes et des décorateurs. Le framework prend également en charge le travail manuel d'injection de dépendances, de validation et d'autorisation des données, en déchargeant le développeur.
Principe d'opération
Voyons comment TypeGraphQL fonctionne en utilisant l'API GraphQL pour la base de données de recettes à titre d'exemple.
Voici à quoi ressemble le schéma en SDL:
type Recipe {
id: ID!
title: String!
description: String
creationDate: Date!
ingredients: [String!]!
}
Réécrivons-le en tant que classe de recette:
class Recipe {
id: string;
title: string;
description?: string;
creationDate: Date;
ingredients: string[];
}
Équipons la classe et les propriétés de décorateurs:
@ObjectType()
class Recipe {
@Field(type => ID)
id: string;
@Field()
title: string;
@Field({ nullable: true })
description?: string;
@Field()
creationDate: Date;
@Field(type => [String])
ingredients: string[];
}
Règles détaillées pour décrire les champs et les types dans la section correspondante de la documentation
Ensuite, nous décrirons les requêtes et mutations CRUD habituelles. Pour ce faire, créons un contrôleur RecipeResolver avec le RecipeService passé au constructeur:
@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;
}
}
}
Ici, le décorateur @Authorized () est utilisé pour restreindre l'accès aux utilisateurs non autorisés (ou insuffisamment privilégiés). Vous pouvez en savoir plus sur l'autorisation dans la documentation .
Il est temps d'ajouter NewRecipeInput et 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;
}
Longueur, Minet @ArrayMaxSize sont des décorateurs de la classe validator qui effectuent la validation de champ automatiquement.
La dernière étape consiste en fait à assembler le circuit. Ceci est fait par la fonction buildSchema:
const schema = await buildSchema({
resolvers: [RecipeResolver]
});
Et c'est tout! Nous avons maintenant un schéma GraphQL entièrement fonctionnel. Sous forme compilée, cela ressemble à ceci:
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!
}
Ceci est un exemple de fonctionnalité de base, en fait, TypeGraphQL peut utiliser un tas d'autres outils de l'arsenal TS. Vous avez déjà vu des liens vers la documentation :)
Quoi de neuf dans la version 1.0
Jetons un coup d'œil aux principaux changements de la version:
Performance
TypeGraphQL est essentiellement une couche d'abstraction supplémentaire sur la bibliothèque graphql-js, et il fonctionnera toujours plus lentement. Mais maintenant, par rapport à la version 0.17, sur un échantillon de 25 000 objets imbriqués, le framework ajoute 30 fois moins de frais généraux - de 500% à 17% avec la possibilité d'accélérer jusqu'à 13%. Certaines méthodes d'optimisation non triviales sont décrites dans la documentation .
Isoler les circuits
Dans les anciennes versions, le schéma était construit à partir de toutes les métadonnées obtenues auprès des décorateurs. Chaque appel suivant à buildSchema a renvoyé le même schéma, construit à partir de toutes les métadonnées disponibles dans le magasin. Désormais, les schémas sont isolés et buildSchema émet uniquement les requêtes directement liées aux paramètres donnés. Autrement dit, en modifiant uniquement le paramètre des résolveurs, nous effectuerons différentes opérations sur les schémas GraphQL.
Directives et extensions
Il existe deux façons d'ajouter des métadonnées aux éléments de schéma: Les directives GraphQL font partie de la SDL et peuvent être déclarées directement dans le schéma. Ils peuvent également le modifier et effectuer des opérations spécifiques, par exemple, générer un type de connexion pour la pagination. Ils sont appliqués à l'aide des décorateurs @Directive et @Extensions et diffèrent dans leur approche de la construction de schémas. Directives de documentation , la documentation des extensions .
Convertisseurs et arguments pour les champs d'interface
La dernière frontière de la compatibilité totale avec GraphQL se trouvait ici. Vous pouvez désormais définir des convertisseurs pour les champs d'interface à l'aide de la syntaxe @ObjectType:
@InterfaceType()
abstract class IPerson {
@Field()
avatar(@Arg("size") size: number): string {
return `http://i.pravatar.cc/${size}`;
}
}
Quelques exceptions sont décrites ici .
Conversion d'entrées et de tableaux imbriqués
Dans les versions précédentes, une instance de la classe d'entrée était créée uniquement au premier niveau d'imbrication. Cela a créé des problèmes et des bogues avec leur validation. Fixé.
Conclusion
Pendant toute la période de développement, le projet est resté ouvert aux idées et aux critiques, open source et incendiaire. 99% du code a été écrit par Michal Litek lui-même, mais la communauté a également apporté une énorme contribution au développement de TypeGraphQL. Aujourd'hui, avec une popularité croissante et un soutien financier, il peut devenir un véritable standard dans son domaine.
Site Twitter de Michal
Github
Doki