GraphQL + TypeScript = amor. TypeGraphQL v1.0





TypeGraphQL v1.0



El 19 de agosto, se lanzó el marco TypeGraphQL, que simplifica el trabajo con GraphQL en Typescript. Durante dos años y medio, el proyecto ha adquirido una sólida comunidad y el apoyo de varias empresas y está ganando popularidad con confianza. Después de más de 650 confirmaciones, tiene más de 5000 estrellas y 400 bifurcaciones en github, fruto del arduo trabajo del desarrollador polaco Michal Litek. En la versión 1.0, el rendimiento mejoró significativamente, los esquemas se aislaron y eliminaron la redundancia anterior, aparecieron dos características principales: directivas y extensiones, el marco fue completamente compatible con GraphQL.



¿Para qué es este marco?



Michal, refiriéndose a su experiencia con GraphQL desnudo, llama al proceso de desarrollo "doloroso" debido a la redundancia y complejidad de modificar el código existente:



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




No suena muy práctico, y con este enfoque, el principal problema es la redundancia del código, lo que dificulta la sincronización de todos los parámetros al escribirlo y agrega riesgos al cambiar. Para agregar un nuevo campo a nuestra entidad, tenemos que iterar sobre todos los archivos: cambiar la clase de la entidad, luego cambiar la parte del esquema y la interfaz. Lo mismo ocurre con los datos de entrada o los argumentos, es fácil olvidarse de actualizar un elemento o cometer un error en un tipo.



Para combatir la redundancia y automatizar todo este trabajo manual, se creó TypeGraphQL. Se basa en la idea de almacenar toda la información en un solo lugar, describiendo el esquema de datos a través de clases y decoradores. El framework también asume el trabajo manual de inyección de dependencias, validación y autorización de datos, descargando al desarrollador.



Principio de funcionamiento



Veamos cómo funciona TypeGraphQL usando la API GraphQL para la base de datos de recetas como ejemplo.



Así es como se ve el esquema en SDL:



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




Vamos a reescribirlo como una clase de Receta:



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




Equipamos la clase y las propiedades con decoradores:



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

        @Field()
        title: string;

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

        @Field()
        creationDate: Date;

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




Reglas detalladas para describir campos y tipos en la sección correspondiente de la documentación



Luego describiremos las consultas y mutaciones CRUD habituales. Para hacer esto, cree un controlador RecipeResolver con el RecipeService pasado al constructor:



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




Aquí, el decorador @Authorized () se usa para restringir el acceso a usuarios no autorizados (o con privilegios insuficientes). Puede leer más sobre la autorización en la documentación .



Es hora de agregar NewRecipeInput y 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;
	}




Longitud, Miny @ArrayMaxSize son decoradores de la clase de validación que realizan la validación de campo automáticamente.



El último paso es ensamblar el circuito. Esto lo hace la función buildSchema:



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




¡Y eso es! Ahora tenemos un esquema GraphQL completamente funcional. En forma compilada, se ve así:



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




Este es un ejemplo de funcionalidad básica, de hecho, TypeGraphQL puede usar un montón de otras herramientas del arsenal de TS. Ya ha visto enlaces a la documentación :)



Novedades de 1.0



Echemos un vistazo rápido a los principales cambios en la versión:



Actuación



TypeGraphQL es esencialmente una capa adicional de abstracción sobre la biblioteca graphql-js, y siempre se ejecutará más lento que ella. Pero ahora, en comparación con la versión 0.17, en una muestra de 25,000 objetos anidados, el marco agrega 30 veces menos gastos generales , del 500% al 17% con la posibilidad de acelerar hasta un 13%. Algunos métodos de optimización no triviales se describen en la documentación .



Aislar circuitos



En versiones anteriores, el esquema se construyó a partir de todos los metadatos obtenidos de los decoradores. Cada llamada posterior a buildSchema devolvió el mismo esquema, creado a partir de todos los metadatos disponibles en la tienda. Ahora los esquemas están aislados y buildSchema emite solo aquellas solicitudes que están directamente relacionadas con los parámetros dados. Es decir, cambiando solo el parámetro resolvers, realizaremos diferentes operaciones en esquemas GraphQL.



Directivas y extensiones



Hay dos formas de agregar metadatos a los elementos del esquema: Las directivas GraphQL son parte del SDL y se pueden declarar directamente en el esquema. También pueden cambiarlo y realizar operaciones específicas, por ejemplo, generar un tipo de conexión para la paginación. Se aplican utilizando los decoradores @Directive y @Extensions y difieren en su enfoque para la construcción de esquemas. Directivas de documentación , la documentación de extensiones .



Convertidores y argumentos para campos de interfaz



Aquí está la última frontera de compatibilidad total con GraphQL. Ahora puede definir convertidores para campos de interfaz usando la sintaxis @ObjectType:



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




Aquí se describen algunas excepciones .



Conversión de matrices y entradas anidadas



En versiones anteriores, se creaba una instancia de la clase de entrada solo en el primer nivel de anidamiento. Esto creó problemas y errores con su validación. Fijo.



Conclusión



Durante todo el período de desarrollo, el proyecto permaneció abierto a ideas y críticas, fuente abierta e incendiaria. El 99% del código fue escrito por el propio Michal Litek, pero la comunidad también hizo una gran contribución al desarrollo de TypeGraphQL. Ahora, con una popularidad creciente y apoyo financiero, puede convertirse en un verdadero estándar en su campo.



Sitio web de Twitter

Github

Doki de

Michal






All Articles