
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:
- GraphQL SDL (Schema Definition Language);
- ORM (Object-Relational Mapping), ;
- , , ...
- , .
- , , .
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
