TypeGraphQL, o básico em uma API de receitas
Tags: typescript, typegraphql, graphql
Published: 2020-11-10 01:41:11

Após passar um tempo aprendendo e tendo uma ótima experiência com o **GraphQL** combinado com **Typescript**, percebi que não havia muito conteúdo sobre o tópico, principalmente em Português. Então decidi compartilhar um pouco do que tenho feito e aprendido com esse Framework incrível.

Sem mais delongas, 

Bora codar!


### Requerimentos

- Uma pequena introdução ao **GraphQL**
- Algumas linha de código com **Typescript**

### O que vamos construir?

- Uma API de receitas

&nbsp;

Antes de tudo, você vai precisar do Node.js instalado.

Acesse [nodejs.org](https://nodejs.org/en/) e faça o download da ultima versão estável.

&nbsp;

Só mais umas palavrinhas antes de começarmos

### GraphQL para recém-chegados

Feito para API, GraphQL tem um enorme poder, que nos permite consultar, inserir e manipular dados em nossa API , de uma maneira fácil e muito performática. 

Não apenas isso mas, GraphQL também nos permite a receber apenas aquilo que desejamos. Sim, esqueça daquelas respostas gigantescas e cheia de informações desnecessárias em sua busca. Sua resposta terá apenas o que você requisitou, o que nos leva a um melhor gerenciamento de nossa API.

![GraphQL](https://dev-to-uploads.s3.amazonaws.com/i/urvqypfjjy168eu83sz0.png)

Sugiro que dê uma olhada em:

[https://graphql.org](https://graphql.org/)

### Por que TypeGraphQL?

Porque ele nos traz uma vida no desenvolvimento de nossa API, muito mais prazerosa e fácil de gerenciar, nos dando a habilidade de construir Schemas a partir de Classes e utilizando Decorators, e claro, tudo excelentemente tipado 🙂

Isso fará mais sentido assim que começarmos.

&nbsp;

BORA! 

&nbsp;

Primeiro de tudo, vamos inicializar um novo projeto. Você pode usar **npm** ou **yarn**.

```bash
yarn init -y
```

&nbsp;

Assim que inicializado, vamos adicionar as belezinhas desse projeto

```bash
yarn add graphql type-graphql reflect-metadata class-validator apollo-server
```

E

```bash
yarn add typescript @types/node nodemon -D
```

&nbsp;

Agora devemos criar o arquivo de configuração do **Typescript**

```bash
tsc --init
```

E com algumas linhas adicionais, ele deve ficar assim:

```json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "lib": ["es2018", "esnext.asynciterable"],
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "strictPropertyInitialization": false,
    "outDir": "./build",
  }
}
```

&nbsp;

O próximo passo é criar o nosso *index.ts*, que será o arquivo de inicialização de nossa aplicação.

Vamos cria-lo dentro de uma pasta */src*. 
Obs. Vamos colocar todo nosso código dentro desta pasta.

src/index.ts

```json
import "reflect-metadata";
```

Uma exigencia do **type-graphql** para que tudo funcione, é importar o "**reflect-metadata**" no topo de nosso arquivo de inicialização.

&nbsp;

Agora vamos criar os scripts que vão nos ajudar a compilar e rodar a nossa API.

Em seu *package.json*, adicione esses scripts, que eu ja te conto o que eles fazem:

```json
"scripts": {
    "watch": "tsc -w",
    "dev": "nodemon build/index.js"
  },
```

Com o script "**watch**", vamos compilar o nosso projeto, que são arquivos **Typescript**, para uma pasta que definimos no arquivo *tsconfig.json*, além de outras opções também.

Note que usamos uma flag "**-w**", que irá continuamente ficar assistindo os arquivos por mudanças, e irá compliar novamente, se houver alguma. 

Agora que compilamos os arquivos **Typescript** para **Javascript**, precisamos rodar o script "**dev**" para que tudo comece a funcionar.

Este script vai rodar o nosso arquivo principal, que agora é um arquivo **Javascript**, dentro de nossa pasta */build*.

&nbsp;

E essa é a nossa configuração inicial 😉

&nbsp;

Só para começarmos, vamos criar um **Hello World** no estilo **GraphQL**.

Dentro de */src* vamos criar uma pasta */Resolvers*. Nela ficarão todos os nossos resolvers, que terão os métodos de nossos tipos Query e Mutations, que vão nos ajudar a manipular nossos dados.

Dentro da pasta */Resolvers*, criamos o arquivo *helloResolver.ts* 

*src/Resolvers/helloResolver.ts* 

```tsx
import { Query, Resolver } from "type-graphql";

@Resolver()
export class helloResolver {
  @Query(() => String)
  hello() {
    return "Hello!";
  }
}
```

Usamos o `@Resolver` Decorator para especificar um Resolver, e dentro dessa classe, nós adicionamos nossas funções, que seram nossas **Queries** ou **Mutations**, que por sua vez serão utilizadas para buscar ou enviar dados para nossa API. 

Em nossa função `hello()` dentro de nosso `helloResolver`, nós estamos buscando dados, portanto, utilizamos o tipo Query.

Podemos até comparar a **Query** com nosso verbo **GET**, em **APIs REST**.

Já o tipo **Mutation**, pode ser comparado com os verbos **POST**, **PUT, PATCH** ou **DELETE**.

&nbsp;

Agora vamos precisar dar um start em nossa aplicação.

Para isso vamos utilizar o **Apollo Server**, que é um servidor próprio para GraphQL e seus **clients**.

Sugiro que dê uma olhada no Apollo Server:

[https://www.apollographql.com/docs/apollo-server/](https://www.apollographql.com/docs/apollo-server/)

&nbsp;

Em nosso arquivo *index.ts*, vamos criar uma função `bootstrap()` que inicializará nosso app.

*src/index.ts*

```tsx
import "reflect-metadata";

import { ApolloServer } from "apollo-server";
import { buildSchema } from "type-graphql";

import { helloResolver } from "./Resolvers/helloResolver";

async function bootstrap() {
  const server = new ApolloServer({
    schema: await buildSchema({
      resolvers: [helloResolver],
    }),
  });

  await server.listen(4000, () => {
    console.log("Apollo Server running at port: 4000");
  });
}

bootstrap().catch((error) => console.log(error));
```

Dentro dela, vamos criar uma instância do Apollo Server. Como uma dependência, vamos criar um **Schema**, que vai receber os nossos **Resolvers**, que no caso, acabamos de criar o primeiro

Por último vamos chamar a função `listen()` do nosso server, passando a porta da aplicação e uma função callback, dizendo que o app está rodando. Se tudo ocorreu bem, claro 🤞🏼

Bora testar!

&nbsp;

Rode os scripts

```bash
yarn watch
```

E

```bash
yarn dev
```

Dê uma olhada em *[http://localhost:4000](http://localhost:4000)*

Tudo rodando! 🏃🏼

&nbsp;

Agora vamos testar nossa primeira **Query**.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/j5ft0gw03hnfi38b2m8w.png)

Incrível! 🔥

&nbsp;

Agora que já está tudo funcionando corretamente, vamos para o nosso projeto!

Primeiro de tudo, vamos criar a nossa **Entidade Recipe**, que será nossa **Model** para o tipo **Recipe**.

Dentro de uma pasta */Entities*, criamos a nossa entidade **Recipe**.

*src/Entities/Recipe.ts*

```tsx
export class Recipe {
  id: string;

  name: string;

  description: string;

  ingredients: Array<string>;
}
```

Essa é nossa classe Recipe.

Agora com o Decorator `@ObjectType`, criamos nosso "Tipo Objeto", para o type-graphql defini-lo e utiliza-lo como um Schema.

E também utilizamos o Decorator `@Field` para marcar as propriedades de nosso objeto.

```tsx
import { Field, ObjectType } from "type-graphql";

@ObjectType()
export class Recipe {
  @Field()
  id: string;

  @Field()
  name: string;

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

  @Field(() => [String])
  ingredients: Array<string>;
}
```

Esses campos podem receber opções, como por exemplo em *description,* a opção *nullable* que define que nossa propriedade pode ser nula.

O Decorator irá auto-inferir os tipos String ou Boolean, mas há uma limitação para tipos genéricos, como Array ou Promises, para esses nos devemos "inferir manualmente", como foi feito no campo *ingredients*.

Antes de criar nosso Recipe Resolver, vamos criar o nosso banco de dados, para armazenarmos nossas receitas.

Para facilitar, não vamos utilizar nenhum banco como MongoDB ou mesmo PostgreSQL. Nosso banco de dados será uma simples Array de Recipe - Isso é para facilitar o tutorial, principalmente para quem está começando 🤓.

Sendo assim, vamos criar nosso incrível banco de dados.

&nbsp;

Criamos um arquivo *data.ts* dentro de nossa pasta */src*

```tsx
import { Recipe } from "./Entities/Recipe";

export const recipeData: Array<Recipe> = [];
```

Apenas exportamos uma **Array de Recipe**.

E é isso para nosso banco. 

Se esse material tiver uma boa relevância para vocês devs, posso fazer algum outro post/tutorial abordando uma API com a implementação de algum banco de dados um pouco mais interessante do que a nossa humilde *recipeData* 😂

Enfim, vamos começar com nosso **Recipe Resolver**.

&nbsp;

Dentro da pasta */Resolvers*, vamos criar nosso *recipeResolver.ts*

*/src/Resolvers/recipeResolver.ts*

```tsx
import { Query, Resolver } from "type-graphql";

import { recipeData } from "../recipeData";

@Resolver()
export class recipeResolver {
	
  @Query(() => [Recipe])
	getRecipes() {
		return recipeData;
	}
}
```

Assim como o nosso Hello World Resolver, vamos criar a classe *recipeResolver.*

Nela começamos com nosso primeiro tipo **Query**, para buscar os dados que temos de receitas.

Assim que salvarmos, e enviarmos a **Query**, teremos o seguinte resultado:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0s5ot6sk3kvafyyuz35q.png)

Vazio como esperado. Agora vamos criar uma **Mutation** para inserirmos novas receitas e assim nossa **Query** `getRecipes()` poderá mostra-los.

&nbsp;

Como já sabemos, para criar nossa receita, vamos precisar de uma **Mutation**

Além disso, também vamos precisar enviar o objeto do tipo **Recipe**, com as informações de nossa receita. 

Vamos definir este objeto como *data* com o `@Arg()` Decorator.

```tsx
import { Query, Mutation, Resolver, Arg } from "type-graphql";

import { recipeData } from "../recipeData";

@Resolver()
export class recipeResolver {
	
  @Mutation(() => Recipe)
  createRecipe(@Arg("data") data: Recipe) { }

}
```

&nbsp;

Agora quando formos enviar nossa Mutation, vamos passar nosso objeto data, para assim conseguirmos armazená-lo em nosso banco.

Mas há um porém. O nosso tipo **Recipe** possui uma propriedade *id*, mas no caso não vamos deixar o cliente enviar esta informação, vamos criá-la em nosso método `createRecipe()`

Para nos auxiliar, vamos utilizar uma biblioteca chamada *uuidv4*

Para instalar essa lib, podemos utilizar nosso gerenciador de pacotes favorito:

```bash
yarn add uuidv4
```

Logo, nossa `createRecipe()` **Mutation** vai ficar assim:

```tsx
import { Query, Mutation, Resolver, Arg } from "type-graphql";
import { v4 as uuidv4 } from "uuid";

import { recipeData } from "../recipeData";

@Resolver()
export class recipeResolver {
	
  @Mutation(() => Recipe)
  createRecipe(@Arg("data") data: Recipe) {
    let newRecipe = { ...data, id: uuidv4() };

    recipeData.push(newRecipe);

    return newRecipe;      
    
  }

}
```

&nbsp;

Não sei se percebeu, mas ainda tem uma coisinha que precisamos mudar...

Nosso argumento *data*, ainda está com o tipo Recipe. 🤔

Vamos resolver isso criando um tipo para *data*

&nbsp;

Criamos uma pasta */Types* e nela criamos o arquivo *RecipeDataType.ts*

*src/Types/RecipeDataType.ts*

```tsx
import { Field, InputType } from "type-graphql";

@InputType()
export class RecipeDataType {
  @Field()
  name: string;

  @Field()
  description: string;

  @Field(() => [String])
  ingredients: Array<string>;
}
```

Diferente de nosso **Tipo Objeto**, para esse vamos utilizar o **Tipo Input** `@InputType`

Agora podemos adicionar esse tipo para nosso *data*

```tsx
import { Query, Mutation, Resolver, Arg } from "type-graphql";
import { v4 as uuidv4 } from "uuid";

import { recipeData } from "../recipeData";
import { RecipeDataType } from "../Types/RecipeDataType";

@Resolver()
export class recipeResolver {
	
  @Mutation(() => Recipe)
  createRecipe(@Arg("data") data: RecipeDataType) {
    let newRecipe = { ...data, id: uuidv4() };

    recipeData.push(newRecipe);

    return newRecipe;      
    
  }

}
```

&nbsp;

Hora de testar!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/aec1wv1qqs5utqhvtr8s.png)

Irado! 🔥

&nbsp;

Para nossas **Mutations** de *delete* e *update*, vamos fazer algo muito parecido, apenas mudando o jeito que manipulamos os dados em nosso banco.

A **Mutation** `deleteRecipe()`, vai precisar de um argumento *id,* para assim, conseguirmos buscar a receita a ser deletada dentro de nosso banco de dados.

```tsx
import { Query, Mutation, Resolver, Arg } from "type-graphql";
import { recipeData } from "../recipeData";

import { RecipeDataType } from "../Types/RecipeDataType";

import { v4 as uuidv4 } from "uuid";

@Resolver()
export class recipeResolver {

@Mutation(() => Boolean)
  deleteRecipe(@Arg("id") id: string): Boolean {
    let recipeIndex = recipeData.findIndex((recipe) => recipe.id === id);

    if (recipeIndex > -1) {
      recipeData.splice(recipeIndex, 1);
      return true;
    }

    return false;
  }

}
```

&nbsp;

Vamos dar uma olhada no resultado

Só não vamos esquecer de criar uma nova receita para podermos deletá-la. Isso acontece porque o nosso "banco de dados" está na memória, e sempre que a aplicação recarregar, vamos perder as nossas deliciosas receitas 😢 Mas não tem problema por agora 😊

Agora com nosso *id*, vamos usá-lo para deletar a receita.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z3qmwe7jbieklosncamj.png)

Brabo!! estamos quase no final 😎

&nbsp;

Por ultimo, nossa Mutation `updateRecipe()`

Para essa, vamos utilizar 2 argumentos, o *id* da receita para ser alterada, e também o *data*, que vai conter as novas informações da receita.

```tsx
import { Query, Mutation, Resolver } from "type-graphql";
import { recipeData } from "../recipeData";

import { RecipeDataType } from "../Types/RecipeDataType";

import { v4 as uuidv4 } from "uuid";

@Resolver()
export class recipeResolver {

	@Mutation(() => Recipe)
  updateRecipe(
    @Arg("id") id: string,
    @Arg("data") data: RecipeDataType
  ) {
    let recipeIndex = recipeData.findIndex((recipe) => recipe.id === id);

    if (recipeIndex > -1) {
      recipeData[recipeIndex] = { ...data, id };

      return recipeData[recipeIndex];
    }

  }

}
```

Checando...

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/updvum63e43i8a94fgy8.png)

&nbsp;

Ta lá! nossa API de receitas. 😎

&nbsp;

Agora podemos dar apenas um toque final...

Como você deve ter percebido, nós não especificamos nenhum tipo de respostas para as nossas **Queries** e **Mutations**, e isso está um pouco feio...

Vamos criar!

&nbsp;

Para esse tipo, vamos utilizar o **Decorator** `@ObjectType()`

Dentro de */Types*, vamos criar *ResponseType.ts*

src/Types/ResponseType.ts

```tsx
import { Field, ObjectType } from "type-graphql";
import { Recipe } from "../Entities/Recipe";

@ObjectType()
export class ErrorType {
  @Field()
  message: string;
}

@ObjectType()
export class ResponseType {
  @Field(() => Recipe, { nullable: true })
  recipe?: Recipe;

  @Field(() => ErrorType, { nullable: true })
  error?: ErrorType;
}
```

Agora precisamos apenas adicionar esse tipo como resposta de nossos métodos em nosso **Resolver**.

Nosso *recipeResolver.ts* completo, ficará assim:

```tsx
import { Arg, Mutation, Query, Resolver } from "type-graphql";
import { v4 as uuidv4 } from "uuid";

import { Recipe } from "../Entities/Recipe";

import { recipeData } from "../recipeData";

import { RecipeDataType } from "../Types/RecipeDataType";
import { ResponseType } from "../Types/ResponseType";

@Resolver()
export class recipeResolver {
  @Query(() => [Recipe])
  recipes() {
    return recipeData;
  }

  @Query(() => ResponseType)
  getRecipe(@Arg("id") id: string): ResponseType {
    let recipe = recipeData.find((recipe) => recipe.id === id);

    if (recipe) {
      return { recipe };
    }

    return {
      error: {
        message: "Recipe not found",
      },
    };
  }

  @Mutation(() => ResponseType)
  createRecipe(@Arg("data") data: RecipeDataType): ResponseType {
    let newRecipe: Recipe = { ...data, id: uuidv4() };

    recipeData.push(newRecipe);

    if (newRecipe) {
      return {
        recipe: newRecipe,
      };
    }

    return {
      error: {
        message: "Could not create your recipe",
      },
    };
  }

  @Mutation(() => ResponseType)
  deleteRecipe(@Arg("id") id: string): ResponseType {
    let recipeIndex = recipeData.findIndex((recipe) => recipe.id === id);

    if (recipeIndex > -1) {
      recipeData.splice(recipeIndex, 1);
      return {};
    }

    return {
      error: {
        message: "Recipe not found",
      },
    };
  }

  @Mutation(() => ResponseType)
  updateRecipe(
    @Arg("id") id: string,
    @Arg("data") data: RecipeDataType
  ): ResponseType {
    let recipeIndex = recipeData.findIndex((recipe) => recipe.id === id);

    if (recipeIndex > -1) {
      recipeData[recipeIndex] = { ...data, id };

      return { recipe: recipeData[recipeIndex] };
    }

    return {
      error: {
        message: "Recipe not found",
      },
    };
  }
}
```

&nbsp;

Vamos dar uma olhada final

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/81tomqabps5mrz4tqevy.png)

DEMAIS! 🔥

&nbsp;

Agora está melhor, mas ainda sim, há espaço para MUITA melhoria. Mas agora é contigo.

Nesse ponto você provavelmente vai estar com o básico de conhecimento de TypeGraphQL, e talvez, assim como eu, percebeu os benefícios de codar sua API GrahpQL com Typescript.

Se tiver afim, faça um fork desse projeto ou mesmo crie um PR com suas alterações e considerações, vou curtir demais trocar conhecimento 👍🏼

&nbsp;

Espero que esse post tenha sido útil. Obrigado.

&nbsp;

Código fonte: 

[vinisaveg/typegraphql-basics](https://github.com/vinisaveg/typegraphql-basics)

&nbsp;

Happy Coding! 🙂