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.
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
O projeto inclui uma API GraphQL de exemplo implementada em Node.js/TypeScript. Para iniciar a API, siga os seguintes passos:
-
Navegue até a pasta
apps/backend
:cd apps/backend
-
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.
Para iniciar o frontend, abra um novo terminal e siga os seguintes passos:
-
Navegue até a pasta
app/web
:cd apps/web
-
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).
Ao abrir o link http://localhost:4000, somos direcionados ao Apollo Sandbox, onde podemos explorar nossa API e desenvolver e testar nossas queries:
Se clicarmos no primeiro ícone no canto esquerdo, podemos ver a definição do Schema com os tipos, queries e mutations:
Nossa API disponibiliza três queries:
feed
: com ela podemos buscar uma lista de tópicostopic(id: ID!)
: busca um tópico pelo seu iduser(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 "
Sinta-se à vontade para adicionar ou remover campos da query e ver como isso é refletido na resposta.
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.
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:
- Angular: apollo-angular
- React: apollo, relay, urql
- Svelte: @urql/svelte
- Vue 3: @urql/vue
- Vue 2: vue-apollo
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.
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 dedata.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:
Você pode ver todas as alterações aqui.
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.
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
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.
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:
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
.