GraphQL + Typescript = любовь. TypeGraphQL v1.0

от автора

ЗTypeGraphQL v1.0

19 августа вышел в релиз фреймворк TypeGraphQL, упрощающий работу с GraphQL на Typescript. За два с половиной года проект обзавёлся солидным комьюнити и поддержкой нескольких компаний и уверено набирает популярность. Спустя более 650 коммитов у него более 5000 звёзд и 400 форков на гитхабе, плод упорной работы польского разработчика Михала Литека. В версии 1.0 значительно улучшилась производительность, схемы получили изоляцию и избавились от прежней избыточности, появились две крупные фичи — директивы и расширения, фреймворк был приведён к полной совместимости с GraphQL.

Для чего этот фреймворк?

Михал, ссылаясь на свой опыт работы с голым GraphQL, называет процесс разработки «болезненным» из-за избыточности и сложности изменения существующего кода:

  1. Сначала нужно создать все необходимые типы GraphQL в формате SDL (Schema Definition Language);
  2. Затем мы создаём модели данных в ORM (Object-Relational Mapping), чтобы описать объекты в базе данных;
  3. После этого надо написать преобразователи для всех запросов, мутаций и полей, что вынуждает нас…
  4. Создавать тайпскриптовые интерфейсы для всех аргументов, инпутов и даже типов объектов.
  5. Только после этого преобразователями можно пользоваться, не забывая при этом вручную проверять рутинные вещи вроде валидации, авторизации и загрузки зависимостей.

Звучит не очень практично, и при таком подходе главная проблема — в избыточности кода, которая затрудняет синхронизацию всех параметров при его написании и добавляет рисков при изменениях. Чтобы добавить новое поле к нашей сущности, мы должны перебрать все файлы: изменить класс сущности, затем изменить часть схемы и интерфейс. То же самое и с входными данными или аргументами, легко забыть обновить один элемент или ошибиться в одном типе.

Для борьбы с избыточностью и автоматизации всего этого ручного труда и был создан TypeGraphQL. В его основе лежит идея хранить всю информацию в одном месте, описывая схему данных через классы и декораторы. Ручной труд по внедрению зависимостей, валидации данных и авторизации фреймворк также берёт на себя, разгружая разработчика.

Принцип работы

Разберём работу TypeGraphQL на примере GraphQL API для базы рецептов.

Так будет выглядеть схема в SDL:

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

Перепишем её в виде класса Recipe:

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

Снабдим класс и свойства декораторами:

    @ObjectType()     class Recipe {         @Field(type => ID)         id: string;          @Field()         title: string;          @Field({ nullable: true })         description?: string;          @Field()         creationDate: Date;          @Field(type => [String])         ingredients: string[];     } 

Подробные правила описания полей и типов в соответствующем разделе документации

Затем мы опишем обычные CRUD запросы и мутации. Для этого создадим контроллер RecipeResolver c 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;             }         }     } 

Здесь декоратор @Authorized() применяется для ограничения доступа для неавторизованных (или обладающих недостаточными правами) пользователей. Подробнее про авторизацию можно почитать в документации.

Пора добавить NewRecipeInput и 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; 	} 

Length, Min и @ArrayMaxSize — это декораторы из класса-валидатора, которые автоматически выполняют проверку полей.

Последний шаг — собственно сборка схемы. Этим занимается функция buildSchema:

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

И всё! Теперь у нас есть полностью рабочая схема GraphQL. В скомпилированном виде она выглядит так:

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

Это пример базовой функциональности, на самом деле TypeGraphQL умеет использовать ещё кучу инструментов из арсенала TS. Ссылки на документацию вы уже видели 🙂

Что нового в 1.0

Вкратце пройдёмся по основным изменениям релиза:

Производительность

TypeGraphQL — это по сути дополнительный слой абстракции над библиотекой graphql-js, и он всегда будет работать медленнее её. Но теперь, по сравнению с версией 0.17, на выборке из 25000 вложенных объектов фреймфорк добавляет в 30 раз меньше оверхеда — с 500% до 17% с возможностью ускорения до 13%. Отдельные нетривиальные способы оптимизации описаны в документации.

Изоляция схем

В старых версиях схема строилась из всех метаданных, получаемых из декораторов. Каждый последующий вызов buildSchema возвращал одну и ту же схему, построенную из всех доступных в хранилище метаданных. Теперь же схемы изолированы и buildSchema выдаёт только те запросы, которые напрямую связаны с заданными параметрами. То есть изменяя лишь параметр resolvers мы поулчаем разные операции над схемами GraphQL.

Директивы и расширения

Это два способа добавить метаданные в элементы схемы: директивы GraphQL являются часть SDL и могут быть объявлены прямо в схеме. Также они могут изменять её и выполнять специфические операции, например, сгенерировать тип соединения для пагинации. Применяются они с помощью декораторов @Directive и @Extensions и различаются подходом к построению схемы. Документация Directives, Документация Extensions.

Преобразователи и аргументы для полей интерфейсов

Последний рубеж полной совместимости с GraphQL лежал здесь. Теперь можно определять преобразователи для полей интерфейса, используя синтаксис @ObjectType:

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

Немногочисленные исключения описаны здесь.

Преобразование вложенных инпутов и массивов

В предыдущих версиях экземпляр класса инпута создавался только на первом уровне вложенности. Это создавало проблемы и баги с их валидацией. Fixed.

Заключение

В течение всего времени разработки, проект оставался открытым к идеям и критике, опенсорсным и зажигательным. 99% кода написал сам Михал Литек, но и сообщество внесло огромный вклад в развитие TypeGraphQL. Теперь, с нарастающей популярностью и финансовой поддержкой, он может стать настоящим стандартом в своей области.

Сайт
Гитхаб
Доки
Твиттер Михала

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/516634/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *