Dev (Back & Front)ARTIGO

Consumindo API GraphQL no ReactJS com TypeScript

Consumindo API GraphQL no ReactJS com TypeScript
Imagem: Luiz Fernando Duarte Junior

GraphQL vem expandindo rapidamente a sua popularidade como padrão para APIs no backend de aplicações web. E uma vez que as chamadas ao GraphQL ainda são nativamente HTTP e você possa consumir as APIs GraphQL usando clientes HTTP como fetch e Axios, é inegável que existem vantagens sólidas de fazê-lo usando clientes específicos para essa tecnologia, principalmente se planeja utilizar TypeScript em conjunto para tipificar os schemas corretamente e deixar tudo type-safe.

Pensando nisso, no tutorial de hoje eu vou te ensinar como consumir APIs GraphQL em aplicações ReactJS com TypeScript e o Apollo Client, sem sombra de dúvida a solução mais popular do mercado para essa finalidade.

É importante entender que este tutorial é voltado a quem já sabe o básico de React, de TypeScript e de GraphQL. Você pode obter esse conhecimento básico nos links em cada nome.

Vamos lá!

#1 –  Setup Inicial

Primeiramente você vai precisar ter uma API GraphQL rodando na sua máquina. Caso não possua nenhuma, use essa aqui, que possui queries e mutations para gestão de usuários. Você pode deixá-la rodando facilmente na sua máquina em uma janela de terminal usando o comando ‘npm start’ após instalar as dependências, sendo que ela vai ficar disponível em http://localhost:3000/graphql já com 100 users cadastrados in-memory.

Segundo, você vai precisar de uma aplicação ReactJS. Eu estarei utilizando uma criada com o toolkit Vite nesse meu exemplo e você pode criar uma também com o comando abaixo.

aspnet
npm create vite@latest

Usei como nome ‘react-graphql’, escolhi React, TypeScript e pronto, sua aplicação já vai estar rodando em http://localhost:5173.

Você precisará instalar algumas dependências na sua aplicação React, a saber:

aspnet
npm install @apollo/client graphql

A biblioteca graphql é a padrão para todas as demais bibliotecas desse protocolo, sendo que não usaremos ela diretamente, é uma dependência da Apollo Client. Esta por sua vez (@apollo/client) expõe pra gente um cliente de comunicação com GraphQL, bem como React Hooks, types e alguns recursos para definição das queries e mutations como veremos adiante.

Crie um arquivo .env na raiz da aplicação e dentro dele uma única variável de ambiente que aponta para a URL da sua API GraphQL.

aspnet
VITE_GRAPHQL_API_URL=http://localhost:3000/graphql

Depois crie uma pasta src/graphql e dentro dela um client.ts, onde configuraremos o Apollo Client, como abaixo.

typescript
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client/core'

const graphqlUri = import.meta.env.VITE_GRAPHQL_API_URL
const httpLink = new HttpLink({ uri: graphqlUri })


const authLink = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem('authToken')


  operation.setContext(({ headers = {} }) => ({
    headers: {
      ...headers,
      Authorization: token ? `Bearer ${token}` : '',
    },
  }))


  return forward(operation)
})


export const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
})

Começamos carregando a URL e iniciando um HttpLink com ela. Depois, eu defini um ApolloLink, que é algo opcional. Ele serve para criar o comportamento automático de carregar um token no localStorage para incluir no cabeçalho Authorization de todas requisições. Nesse tutorial não vamos usar realmente isso mas é um exemplo importante porque muitas APIs GraphQL possuem rotas autenticadas.

Por fim, inicializamos o ApolloClient em si, passando o link e o cache, o que vai beneficiar a experiência do usuário ao usar a nossa aplicação. Esse ApolloClient deve ser informado em um contexto que vai permitir depois o uso de ReactHooks do Apollo em nossa aplicação. Abaixo como fica a configuração do contexto no main.tsx.

typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ApolloProvider } from '@apollo/client/react'
import App from './App.tsx'
import { apolloClient } from './graphql/client.ts'


createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ApolloProvider client={apolloClient}>
      <App />
    </ApolloProvider>
  </StrictMode>,
)

Também vale colocar nessa pasta src/graphql um User.ts para o nosso type de User que vai ser usado em diversos locais.

typescript
export type User = {
    id: string
    name: string
    email: string
}

E por fim, nosso App.tsx vai exibir outros dois componentes que ainda não temos, um para listar usuários, outro para cadastrar usuários.

javascript
import UserForm from './components/UserForm'
import UsersList from './components/UsersList'


function App() {
    return (
        <>
            <h1>React + GraphQL</h1>
            <hr />
            <UserForm />
            <UsersList />
        </>
    )
}


export default App

E com isso terminamos o setup inicial.

#2 – GraphQL Query via ReactJS

Basicamente o que você precisa aprender é a fazer dois tipos de operações através do ReactJS utilizando ApolloClient: Query e Mutation. Assim, vamos fazer um de cada apenas para evitar repetições desnecessárias, mas havendo interesse em praticar, pode olhar o schema da API de exemplo e implementar toda ela repassando os passos deste tutorial. Aliás, isso é um ótimo exercício.

Nossa Query será uma consulta a getUsers, retornando x usuários de uma determinada página. Para fins de organização, vamos criar um arquivo graphql/queries.ts, onde vamos exportar uma constante com a query getUsers tipada.

typescript
import { gql } from '@apollo/client/core'

export const GET_USERS = gql`
  query GetUsers($page: Int, $pageSize: Int) {
    getUsers(page: $page, pageSize: $pageSize) {
      id
      name
      email
    }
  }
`

Se você nunca usou Tagged Templates antes pode achar o uso da função gql um tanto estranha. Basicamente ele é um Template String tradicional, mas usando uma tag function para processar a substituição dos placeholders internos do template. O conteúdo do template em si dispensa apresentações, é exatamente a mesma sintaxe que você veria usando o Postman para testar essa API GraphQL, por exemplo.

Agora antes de fazermos o componente que vai realizar a query acima, como ele é uma listagem de users, vamos criar primeiro o subcomponente que exibe apenas os dados de um usuário. Crie um src/components/UserItem.tsx com o seguinte conteúdo:

typescript
type UserItemProps = {
    user: {
        id: string
        name: string
        email: string
    }
}


export default function UserItem({ user }: UserItemProps) {
    return (
        <tr>
            <td>{user.id}</td>
            <td>{user.name}</td>
            <td>{user.email}</td>
        </tr>
    )
}

Nenhuma explicação é necessária aqui. Agora vamos importar esse componente no topo do novo arquivo src/components/UsersList.tsx que você vai criar, bem como outras dependências:

typescript
import type { TypedDocumentNode } from '@apollo/client/core'
import { useQuery } from '@apollo/client/react'
import { GET_USERS } from '../graphql/queries.ts'
import UserItem from './UserItem.tsx'
import { type User } from '../graphql/User.ts'

Também vamos precisar neste mesmo arquivo de alguns types e algumas constantes, todos sendo usados mais adiante.

typescript
type GetUsersData = {
    getUsers: User[]
}


type GetUsersVars = {
    page?: number
    pageSize?: number
}


const FIRST_PAGE = 1
const PAGE_SIZE = 10

Dentro da function UsersList, precisamos declarar três coisas, que explico a seguir.

typescript
const getUsersQuery = GET_USERS as TypedDocumentNode<GetUsersData, GetUsersVars>
const { data, loading, error } = useQuery(getUsersQuery, {
    variables: {
        page: FIRST_PAGE,
        pageSize: PAGE_SIZE,
    },
})

const users = data?.getUsers ?? []

Primeiro, vamos usar a query GET_USERS que criamos anteriormente com auxílio do gql tipando-a com TypedDocumentNode. Isso vai garantir type-safety e também um autocomplete preciso no passagem de parâmetros e no uso de retorno de queries. Passando a getUsersQuery tipada para o hook useQuery, nós enviaremos via Apollo Client a consulta correta, informando a página que queremos carregar e a quantidade de elementos. Como retorno, temos data (os itens), loading (true ou false indicado se está aguardando retorno) e error (somente preenchido se der erro na consulta).

Com essas três informações obtidas com o hook useQuery, nós temos tudo que é necessário para renderizar os usuários na página, inclusive comecei fazendo um fallback para array vazio caso não tenhamos ainda nenhum usuário em data.

Agora falando da renderização em si, vou criar uma tabela muuuito simples, apenas para vermos o resultado na tela, tratando também os estados de loading e error.

typescript
return (
    <>
        <h2>Users List</h2>


        {loading && <p>Loading users...</p>}
        {error && <p>Failed to load users: {error.message}</p>}


        {!loading && !error && users.length === 0 && <p>No users found.</p>}


        {!loading && !error && users.length > 0 && (
            <table>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    {users.map((user) => (
                        <UserItem key={user.id} user={user} />
                    ))}
                </tbody>
            </table>
        )}
        <hr />
    </>
)

O resultado você confere no browser se tudo deu certo.

#3 – GraphQL Mutation via ReactJS

Agora que aprendemos como fazer uma query GraphQL via ReactJS e Apollo Client, vamos ver como fazer uma Mutation, ou sejam, operações que alteram dados.

O processo é bem parecido, mas para fins de organização, vamos guardar as mutations em um novo arquivo src/graphql/mutations.ts com o conteúdo abaixo.

typescript
import { gql } from '@apollo/client/core'

export const CREATE_USER = gql`
  mutation CreateUser($userInput: UserInputData) {
    createUser(userInput: $userInput) {
      id
      name
      email
    }
  }
`

Note que não é muito diferente das queries não, então dispensamos explicações adicionais, considerando que é esperado que você já conheça a sintaxe básica do GraphQL.

Agora vamos criar um src/components/UserForm.tsx com as importações necessárias:

typescript
import { useState } from 'react'
import type { TypedDocumentNode } from '@apollo/client/core'
import { useMutation } from '@apollo/client/react'
import { CREATE_USER } from '../graphql/mutations.ts'
import type { User } from '../graphql/User.ts'

E depois os types que este componente precisa:

typescript
type CreateUserData = {
createUser: User
}

type CreateUserVars = {
name: string
email: string
password: string
}

Por fim, dentro da função UserForm, vamos definir o state do usuário que vai ser cadastrado, a variável da mutation que criamos anteriormente, devidamente tipada com TypedDocumentNode e o uso do hook useMutation, que explico melhor mais adiante.

typescript
const [user, setUser] = useState<CreateUserVars>({} as CreateUserVars)

const createUserMutation = CREATE_USER as TypedDocumentNode<CreateUserData, CreateUserVars>
const [createUser, { data, loading, error }] = useMutation(createUserMutation)

Diferente do hook useQuery, que retorna logo de cara os dados da consulta realizada, o hook useMutation retorna uma função com o nome da mutation (createUser) que deve ser chamada sempre que você quiser enviar a requisição de mutação pra API. Assim, devemos usar esse createUser dentro do click de algum botão de submit do formulário de cadastro, que vamos criar logo mais.

A função de click do botão pode ser algo como abaixo, prevenindo o submit padrão e enviando as propriedades do state do usuário que vai ser preenchido.

typescript
async function handleSubmit(event: React.SubmitEvent<HTMLFormElement>) {
event.preventDefault()


await createUser({
variables: {
name: user.name.trim(),
email: user.email.trim(),
password: user.password.trim(),
},
})


setUser({} as CreateUserVars)
}

Já a renderização do formulário é bem simples neste exemplo e não tem nenhuma novidade dado que você já deve ter conhecimento básico de React para acompanhar este tutorial.

typescript
return (
<>
<h2>New User</h2>


<form onSubmit={handleSubmit}>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
value={user.name}
onChange={handleChange}
required
/>


<br />


<label htmlFor="email">E-mail</label>
<input
type="email"
id="email"
name="email"
value={user.email}
onChange={handleChange}
required
/>


<br />


<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
value={user.password}
onChange={handleChange}
required
/>


<br />


<button type="submit" disabled={loading}>
{loading ? 'Creating user...' : 'Create user'}
</button>
</form>


{error && <p>Failed to create user: {error.message}</p>}
{data?.createUser && <p>User created: {data.createUser.name}</p>}


<hr />
</>
)

Note que usei acima uma função para o binding dos campos com o state de user, que represento abaixo.

javascript
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = event.target
setUser((currentUser) => ({ ...currentUser, [name]: value }))
}

Vale ressaltar também o uso adequado dos states de data (retorno da mutation), error (em caso de erro na mutation) e de loading (indicando que a requisição está aguardando retorno).

O resultado você confere no browser.

Note que como nossa listagem traz os primeiros 10 elementos de um total de 100 que tem por padrão na API (mockados), você não vai ver seu usuário na lista, a menos que modifique o código da API ou que mande exibir a página de número 11 na listagem.

E com isso terminamos este tutorial e espero que tenha lhe ajudado a entender como usar ReactJS em conjunto de APIs GraphQL com Apollo Client.

Até a próxima!

Pós-graduado em computação, trabalha com software desde 2006 nas mais variadas tecnologias. Empreendedor, autor e professor, quando não está ocupado programando, está escrevendo ou gravando sobre programação para seu canal e blog LuizTools.

Ver perfil