Skip to content

allanmaral/topicql

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TopicQL - Um estudo de GraphQL

Introdução

O TopicQL é um projeto de exemplo que demonstra o uso do GraphQL no frontend com o framework Vue e no backend com Node.js e TypeScript. Este tutorial fornecerá instruções passo a passo sobre como configurar e executar o projeto, bem como como utilizar o GraphQL para se conectar à API.

Rodando a aplicação

Antes de iniciar a aplicação, é necessário instalar as dependências. Certifique-se de estar na raiz do projeto e execute o seguinte comando no terminal:

npm install

Iniciando a API

O projeto inclui uma API GraphQL de exemplo implementada em Node.js/TypeScript. Para iniciar a API, siga os seguintes passos:

  1. Navegue até a pasta apps/backend:

    cd apps/backend
  2. Prepare o banco de dados SQLite e inicie o servidor:

    npm run db:prepare npm run dev

    Agora, o backend estará ouvindo as requisições em http://localhost:4000 e um banco de dados SQLite terá sido criado.

Iniciando o Frontend

Para iniciar o frontend, abra um novo terminal e siga os seguintes passos:

  1. Navegue até a pasta app/web:

    cd apps/web
  2. Inicie o servidor:

    npm run dev

    Você deverá ver algo como:

    VITE v4.3.9 ready in 243 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help

    Com isso, o servidor de desenvolvimento do Vue irá compilar o código e disponibilizará a aplicação em http://localhost:5173/ (a porta pode variar de acordo com a disponibilidade).

Explorando a API GraphQL

Ao abrir o link http://localhost:4000, somos direcionados ao Apollo Sandbox, onde podemos explorar nossa API e desenvolver e testar nossas queries:

Sandbox

Se clicarmos no primeiro ícone no canto esquerdo, podemos ver a definição do Schema com os tipos, queries e mutations:

Schema

Nossa API disponibiliza três queries:

  • feed: com ela podemos buscar uma lista de tópicos
  • topic(id: ID!): busca um tópico pelo seu id
  • user(id: ID!): busca um usuário pelo id

Se quisermos montar o feed para o nosso app, poderíamos usar o feed e selecionar os dados desejados. Volte para o Explorer e insira a seguinte query no editor:

query Feed { feed { id content createdAt author { id username avatarUrl } } }

Em seguida, clique no botão "▶️ Feed". Você deve ver o resultado da query aparecer na direita:

Query do Feed

Sinta-se à vontade para adicionar ou remover campos da query e ver como isso é refletido na resposta.

Executando a primeira Query no frontend

Se você trabalhar em algum projeto que usa GraphQL, provavelmente você vai ter que usar alguma biblioteca que cuida da comunicação com o backend. Chamamos essas libs de Clients. Temos algumas opções, como vamos ver daqui a pouco, mas antes de usar um client, vamos fazer uma query manualmente para ver que não existe nenhuma mágica ou tecnologia diferente por trás.

No nosso projeto, vamos abrir o arquivo apps/web/src/modules/feed/pages/FeedPage.vue e começar a testar. O GraphQL, por trás dos panos, usa requisições POST, então podemos reproduzir uma das queries que vimos no sandbox usando a função fetch do navegador. Vamos adicionar o seguinte código no script do nosso componente:

import { ref } from 'vue'; import TopicList from '@/components/TopicList.vue'; import LoadingIndicator from '@/components/LoadingIndicator.vue'; const topics = ref<any>(); const loading = ref(true); fetch('http://localhost:4000/', { headers: { 'content-type': 'application/json', }, body: JSON.stringify({ query: `#graphql  query Feed {  feed {  id  content  createdAt  author {  id  username  avatarUrl  }  }  }  `, }), method: 'POST', }) .then((response) => response.json()) .then((json) => { topics.value = json.data.feed; loading.value = false; });

Para visualizar os dados, podemos adicionar os seguintes elementos no nosso template:

<template v-if="loading"> <LoadingIndicator></LoadingIndicator> </template> <template v-else-if="topics"> <TopicList :topics="topics"></TopicList> </template>

O código anterior usa o fetch para fazer um POST na API, passando no corpo da requisição um objeto com o campo query que é a string usada como exemplo no Sandbox. O JSON.stringify e o comentário #graphql são apenas conveniências para simplificar a escrita do payload e fazer com que o editor de texto destaque a string como se fosse GraphQL.

Caso tenha alguma dúvida, o código completo pode ser encontrado aqui.

Clients GraphQL

Como vimos no exemplo anterior, não precisamos de nenhuma biblioteca para usar o GraphQL, afinal, ele é uma linguagem agnóstica de plataforma, o que significa que podemos usá-la em qualquer framework frontend. Mas lidar com todas as peculiaridades do GraphQL pode ser um pouco trabalhoso, principalmente quando entramos em casos de uso mais avançados, como deduplicação de requisições e cache. Por isso, é bem comum usarmos alguma biblioteca para abstrair esses detalhes. Abaixo estão algumas opções para clientes GraphQL em diferentes frameworks:

URQL e Vue

Nesse tutorial, vamos utilizar o URQL como client do GraphQL para simplificar as operações. Vamos usar o guia de instalação dos bindings para Vue como referência. Você pode encontrar o guia completo aqui.

O primeiro passo é instalar as dependências:

npm install @urql/vue graphql

Com as dependências instaladas, vamos criar uma instância do client e um plugin Vue. Crie o arquivo apps/web/src/lib/graphql/client.ts com o seguinte conteúdo:

import urql, { Client, cacheExchange, fetchExchange } from '@urql/vue'; import { App } from 'vue'; export const client = new Client({ url: 'http://localhost:4000/', exchanges: [cacheExchange, fetchExchange], }); export function graphql(app: App) { app.use(urql, client); }

Aqui, criamos um client usando a classe Client do pacote @urql/vue e configuramos ele para apontar para a URL da nossa API. Em seguida, criamos uma função que funciona como um plugin do Vue e instala o URQL e provê nosso cliente para toda a aplicação.

Por fim, vamos instalar nosso plugin no arquivo apps/web/src/main.ts:

 import './assets/style/style.css'; +import { graphql } from './lib/graphql/client.ts'; const app = createApp(App); ... app.use(router); +app.use(graphql); app.mount('#app');

Com isso, já estamos prontos para usar o URQL na nossa aplicação.

Atualizando a query com URQL

Agora que temos o URQL instalado, podemos substituir o código manual da nossa query GraphQL por meio do composable useQuery. O useQuery é uma funcionalidade do URQL que nos permite fazer consultas GraphQL no frontend de forma mais simples.

Primeiro, vamos importar as dependências necessárias e definir nossa query.

import { computed } from 'vue'; import { useQuery, gql } from '@urql/vue';

Agora, podemos usar o useQuery para fazer a consulta GraphQL:

const { data, fetching, error } = useQuery({ query: gql`  query Feed {  feed {  id  content  createdAt  author {  id  username  avatarUrl  }  }  }  `, });

O useQuery é uma função que recebe um objeto de configuração contendo a query GraphQL. Passamos a query como uma string usando o gql do URQL, que permite destacar e formatar a query de forma mais legível.

O useQuery retorna um objeto que contém várias propriedades:

  • data: Contém os dados retornados pela query após ela ser concluída. Esses dados podem ser acessados por meio de data.value.
  • fetching: É uma flag booleana que indica se a requisição está em andamento (true) ou se já foi concluída (false). Isso pode ser usado para exibir um indicador de carregamento enquanto a requisição está sendo processada.
  • error: Se houver algum erro durante a execução da query, ele será armazenado aqui. Caso contrário, será null.

Agora que temos os dados da consulta disponíveis em data, podemos usá-los no nosso template para exibir os tópicos na página. Para isso, criamos uma variável computada topics que extrai os tópicos do objeto data.

const topics = computed(() => data.value?.feed);

Com isso, podemos atualizar nosso template para usar os novos dados:

<template v-if="fetching"> <LoadingIndicator></LoadingIndicator> </template> <template v-else-if="error"> <ErrorMessage /> </template> <template v-else-if="topics"> <TopicList :topics="topics"></TopicList> </template>

Não se esqueça de importar o componente de mensagagem de erro:

import ErrorMessage from '@/components/ErrorMessage.vue';

O resultado dever ser alguma coisa assim:

Feed do App

Você pode ver todas as alterações aqui.

Geração de Código

Agora que o nosso frontend está conectado à API GraphQL, podemos extrair informações do Schema do GraphQL e gerar definições de tipo automaticamente. Para isso, utilizaremos a ferramenta graphql-codegen, que escaneia o código por documentos GraphQL e extrai os tipos de acordo com o Schema.

Instalação do graphql-codegen

Primeiro, vamos instalar as seguintes dependências:

npm install -D @graphql-codegen/cli @graphql-codegen/client-preset

Em seguida, crie um arquivo de configuração chamado codegen.ts na raiz do frontend. Esse arquivo indicará ao codegen que ele deve escanear todos os arquivos .vue e gerar definições de tipo na pasta ./src/gql:

import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'http://localhost:4000', documents: ['src/**/*.vue'], ignoreNoDocuments: true, // for better experience with the watcher generates: { './src/gql/': { preset: 'client', config: { useTypeImports: true, }, }, }, }; export default config;

Para finalizar a configuração, adicione os seguintes comandos no package.json do frontend:

"scripts": { "codegen": "graphql-codegen", "codegen:watch": "graphql-codegen --watch" },

Agora você pode executar o seguinte comando para que o codegen gere automaticamente os tipos sempre que os arquivos forem alterados:

npm run codegen:watch

Utilizando os tipos gerados no frontend

Com as definições de tipo geradas, podemos aproveitar ao máximo o GraphQL no frontend. Em vez de utilizar diretamente a tag gql do @urql/vue, troque-a pela função graphql gerada pelo codegen:

-const { data, fetching, error } = useQuery({ - query: gql` - query Feed { - feed { - id - content - createdAt - author { - id - username - avatarUrl - } - replies { - author { - id - username - avatarUrl - } - } - } - } - `, -}); +const feedQueryDocument = graphql(/* GraphQL */ ` + query Feed { + feed { + id + content + createdAt + author { + id + username + avatarUrl + } + replies { + author { + id + username + avatarUrl + } + } + } + } +`); +const { data, fetching, error } = useQuery({ + query: feedQueryDocument, +});

Agora você pode utilizar as definições do GraphQL de forma automática no frontend. Utilizando o codegen, você economiza tempo e evita erros relacionados aos tipos.

Criando uma Mutation

Já vimos como ler dados com o GraphQL, agora está na hora de criar alguma coisa! Vamos implementar a feature de criação de um novo tópico para aprender a fazer nossa primeira Mutation.

Antes de mexer no frontend, vamos abrir o Sandbox e escrever a Mutation:

mutation CreateTopic($topic: TopicInput!) { createTopic(topic: $topic) { id content createdAt author { id - username - avatarUrl } } }

Nela, usamos o $topic: TopicInput! para definir que vamos passar os dados do novo tópico como variáveis na requisição.

Abra o arquivo apps/web/src/modules/post/pages/CreateTopicPage.vue, você deve ver que já temos o template e parte do script definidos.

Vamos adicionar o seguinte código:

import { graphql } from '@/gql'; import { useMutation } from '@urql/vue'; const createTopicMutationDocument = graphql(/* GraphQL */ `  mutation CreateTopic($topic: TopicInput!) {  createTopic(topic: $topic) {  id  content  createdAt  author {  id - username - avatarUrl  }  }  } `); const { executeMutation: createTopic } = useMutation( createTopicMutationDocument );

Aqui, copiamos a mutation que foi feita no Sandbox e usamos o composable useMutation para criar um método createTopic que vai executar a mutation ao ser chamado.

Agora podemos editar nosso handleSubmit para executar o comando:

const loading = ref(false); async function handleSubmit(content: string) { try { loading.value = true; createTopic({ topic: { content } }); router.push('/'); } catch (error) { console.error('Ops...', error); } finally { loading.value = false; } }

Vale notar que, graças à geração de tipos automática, o editor já sabe que devemos passar um objeto com os dados do tópico como parâmetro do createTopic.

Ao testar a aplicação, vemos que ao clicar em "Postar", o método handleSubmit é chamado e um novo tópico é criado:

Criando um tópico

Próximas features

Esse projeto tem mais algumas features para serem implementadas. Fique à vontade para explorar a API e treinar o que aprendeu. Se você se sentir perdido, pode encontrar a solução na branch solution.

Referências e Links úteis

About

Um estudo de GraphQL

Topics

Resources

Stars

Watchers

Forks

Contributors