Fullstack com Node.js, React e GraphQL – 10: Apollo Client

Fala, pessoal! Nesta parte 10 da série Fullstack com Node.js, React e GraphQL vamos finalmente integrar o backend e o frontend, usando o Apollo Client para buscar os dados no servidor e fornecê-los para os componentes React.

Integração da autenticação
Antes de começar a usar o Apollo Client precisamos integrar a autenticação no front e back.
Hoje, no ambiente de desenvolvimento, temos que dar npm start
em duas aplicações: o frontend e backend. Cada uma roda em uma porta (backend em 5000 e frontend em 5001).
Rodando tudo na porta 5000
Vamos adicionar um http proxy no backend para que o frontend fique disponível em http://localhost:5000/app.
Instalamos no backend a dependência:
npm i http-proxy-middleware
E habilitamos na rota /app:
// proxy
app.use(
'/app',
authMiddleware({ redirect: true }),
createProxyMiddleware({ target: 'http://localhost:5001', changeOrigin: true })
)
Com essa configuração tudo que for acessado dentro da rota /app, internamente vai ser buscado em http://localhost:5001, todo o nosso frontend, incluindo os assets.
Veja que, antes do midleware de proxy, adicionamos o middleware de autenticação, portanto esta rota fica protegida. Se tentar acessá-la sem estar logado, ele redireciona para a raiz, onde vai ter a tela de login.
Tela de login
Adicionamos uma tela estática de login no backend, em public/login/index.html:

O botão login manda para /auth/google, que dispara o fluxo de login pelo Google.
Rota raiz e logout
Por fim, atualizamos a rota raiz para redirecionar para /app se estiver logado ou /login, caso contrário:
app.get('/', (req, res) => {
if (req.isAuthenticated()) {
res.redirect('/app')
} else {
res.redirect('/login')
}
})
Para sair da aplicação, basta ter um link para /auth/logout, que dispara o fluxo de logout.
Apollo Client
O Apollo Client é a parte do Apollo que fica no frontend. Ele vai enviar queries ao nosso servidor GraphQL e prover os dados para os componentes React.
Temos que instalar os pacotes no projeto de frontend:
npm install @apollo/client graphql
No index.ts fazemos a inicialização da lib, dizendo onde está o servidor GraphQL e como cachear os dados (por padrão a gente usa o cache em memória).
Devemos também adicionar o ApolloProvider
em volta do componente App, para que toda a aplicação tenha acesso ao contexto do Apollo Client:
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
const client = new ApolloClient({
uri: 'http://localhost:5000/graphql',
cache: new InMemoryCache(),
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('app')
)
Fazendo as queries para buscar os dados
Com o Apollo Client configurado, podemos fazer queries nos nossos componentes para buscar dados no servidor.
Vamos começar com estes cards na home:

Primeiro a gente declara uma query fora do componente usando a função gql
:
import { gql, useQuery } from '@apollo/client'
const balancesQuery = gql`
query {
investments {
balance
invested
}
}
`
Esta query busca todos os investimentos, trazendo o saldo atual e o total investido de cada um.
Já dentro do componente a gente usa o hook useQuery
do Apollo para pegar os dados resultantes desta query:
const { loading, data, error } = useQuery<{
investments: { balance: number; invested: number }[]
}>(balancesQuery)
Veja que o useQuery
permite especificar o tipo de dado que a query deve retornar através do parâmetro de tipo { investments: { balance: number; invested: number }[]; }
O hook useQuery
retorna três informações:
- loading: booleano indicando se a requisição com o servidor ainda está em andamento
- error: caso a requisição tenha retornado um erro
- data: resultado da query caso a requisição tenha ocorrido com sucesso
Com isso podemos tratar cada caso. No nosso caso, fizemos um tratamento bem simples do loading e error para ilustrar:
if (loading) {
return <p>Carregando...</p>
}
if (error) {
return <p>{error.toString()}</p>
}
Como o data vai trazer os valores por investimento, vamos somar tudo para exibir nos cards o patrimônio total e fazer os cálculos de lucro e rentabilidade.
A gente poderia fazer esse cálculo no backend, mas como não são complexos, vamos fazer no client mesmo.
// saldo total da carteira
const totalBalance =
data?.investments.reduce((prev, cur) => prev + cur.balance, 0) || 0
// valor investido total
const totalInvested =
data?.investments.reduce((prev, cur) => prev + cur.invested, 0) || 0
// lucro em R$
const profit = totalBalance - totalInvested
// rentabilidade em %
const profitMargin = profit / totalInvested
Note que usamos optional chaining - data?.investments
- pois para o TypeScript data
pode não estar definido. Como já tratamos os casos de loading
e error
, neste ponto podemos assumir que data
terá algum valor.
Feito isso podemos exibir os valores em um componente Card
que criamos:
<Card title="Patrimônio" value={totalBalance} />
Para o gráfico na home a ideia é a mesma:

A query para isso vai trazer os nomes e atualizações de saldo dos investimentos:
const investmentsQuery = gql`
query {
investments {
id
name
balanceUpdates(order: [["date", "ASC"]]) {
date
amount
}
}
}
`
Por fim, a tabela com informações dos investimentos é bem simples:

A query traz nome, saldo e valor investido de cada investimento, bem como o nome da corretora:
const investmentsQuery = gql`
query {
investments {
id
name
broker {
name
}
balance
invested
}
}
`
Olha aí a beleza do GraphQL. Pedimos exatamente os dados que precisamos, inclusive dados relacionados, como o nome da corretora de cada investimento, e o backend retorna exatamente isso, nem mais, nem menos.
Resultado final
O código do projeto até este ponto está em: https://github.com/doug2k1/my-money/tree/v10.0.0
No próximo capítulo
Na próxima parte vamos adicionar testes no nosso frontend.
Stay tuned!
Tags: full-stack|graphql|nodejs|react