GraphQL + Typescript = amor. TypeGraphQL v1.0





TypeGraphQL v1.0



Em 19 de agosto, a estrutura TypeGraphQL foi lançada, o que simplifica o trabalho com GraphQL em Typescript. Por dois anos e meio, o projeto adquiriu uma sólida comunidade e apoio de várias empresas e está ganhando popularidade com segurança. Depois de mais de 650 commits, ele tem mais de 5.000 estrelas e 400 garfos no github, fruto do trabalho árduo do desenvolvedor polonês Michal Litek. Na versão 1.0, o desempenho melhorou significativamente, os esquemas ficaram isolados e se livraram da redundância anterior, dois recursos principais apareceram - diretivas e extensões, o framework foi trazido para compatibilidade total com GraphQL.



Para que serve esta estrutura?



Michal, referindo-se à sua experiência com GraphQL básico, chama o processo de desenvolvimento de "doloroso" devido à redundância e complexidade de modificar o código existente:



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




Não parece muito prático e, com essa abordagem, o principal problema é a redundância do código, o que dificulta a sincronização de todos os parâmetros ao escrevê-lo e adiciona riscos ao alterar. Para adicionar um novo campo à nossa entidade, temos que iterar sobre todos os arquivos: mude a classe da entidade, depois mude a parte do esquema e a interface. É o mesmo com dados de entrada ou argumentos, é fácil esquecer de atualizar um elemento ou cometer um erro em um tipo.



Para combater a redundância e automatizar todo esse trabalho manual, o TypeGraphQL foi criado. Baseia-se na ideia de armazenar todas as informações em um único local, descrevendo o esquema de dados por meio de classes e decoradores. O framework também assume o trabalho manual de injeção de dependência, validação de dados e autorização, descarregando o desenvolvedor.



Princípio da Operação



Vamos dar uma olhada no trabalho de TypeGraphQL usando o exemplo da API GraphQL para o banco de dados de receita.



Esta é a aparência do esquema no SDL:



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




Vamos reescrevê-lo como uma classe de receita:



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




Vamos equipar a classe e as propriedades com 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[];
    }




Regras detalhadas para descrever campos e tipos na seção correspondente da documentação A



seguir, descreveremos as consultas e mutações CRUD usuais. Para fazer isso, vamos criar um controlador RecipeResolver com o RecipeService passado para o construtor:



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




Aqui, o decorador @Authorized () é usado para restringir o acesso a usuários não autorizados (ou com privilégios insuficientes). Você pode ler mais sobre autorização na documentação .



É hora de adicionar NewRecipeInput e 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;
	}




comprimento, Mine @ArrayMaxSize são decoradores da classe validadora que fazem validação de campo automaticamente.



A última etapa é realmente montar o circuito. Isso é feito pela função buildSchema:



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




E é isso! Agora temos um esquema GraphQL totalmente funcional. Na forma compilada, tem a seguinte aparência:



	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 é um exemplo de funcionalidade básica; na verdade, o TypeGraphQL pode usar várias outras ferramentas do arsenal de TS. Você já viu links para a documentação :)



O que há de novo em 1.0



Vamos dar uma olhada rápida nas principais mudanças na versão:



atuação



TypeGraphQL é essencialmente uma camada adicional de abstração sobre a biblioteca graphql-js e sempre rodará mais devagar do que isso. Mas agora, em comparação com a versão 0.17, em uma amostra de 25.000 objetos aninhados, o framework adiciona 30 vezes menos overhead - de 500% a 17% com a possibilidade de acelerar até 13%. Alguns métodos de otimização não triviais são descritos na documentação .



Isolando circuitos



Em versões anteriores, o esquema era construído a partir de todos os metadados obtidos dos decoradores. Cada chamada subsequente para buildSchema retornou o mesmo esquema, criado a partir de todos os metadados disponíveis no armazenamento. Agora os esquemas estão isolados e buildSchema emite apenas as solicitações que estão diretamente relacionadas aos parâmetros fornecidos. Ou seja, alterando apenas o parâmetro resolvers, realizaremos diferentes operações nos esquemas GraphQL.



Diretivas e extensões



Existem duas maneiras de adicionar metadados aos elementos do esquema: As diretivas GraphQL fazem parte do SDL e podem ser declaradas diretamente no esquema. Eles também podem alterá-lo e executar operações específicas, por exemplo, gerar um tipo de conexão para paginação. Eles são aplicados usando os decoradores @Directive e @Extensions e diferem em sua abordagem para a construção do esquema. Diretivas de documentação , a documentação de extensões .



Conversores e argumentos para campos de interface



A última fronteira de compatibilidade total com GraphQL está aqui. Agora você pode definir conversores para campos de interface usando a sintaxe @ObjectType:



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




Algumas exceções são descritas aqui .



Converter entradas e matrizes aninhadas



Nas versões anteriores, uma instância da classe de entrada era criada apenas no primeiro nível de aninhamento. Isso criou problemas e bugs com sua validação. Fixo.



Conclusão



Durante todo o período de desenvolvimento, o projeto permaneceu aberto a ideias e críticas, open source e incendiário. 99% do código foi escrito pelo próprio Michal Litek, mas a comunidade também fez uma grande contribuição para o desenvolvimento do TypeGraphQL. Agora, com o aumento da popularidade e do apoio financeiro, pode se tornar um verdadeiro padrão em seu campo.



Site do Twitter do

Github

Doki de

Michal






All Articles