Automatizando seu Frontend com Next, GraphQL, Apollo e Codegen

Automatizando a criação de hooks e funções para chamadas server e client side no seu frontend

a imagem mostra um notebook aberto em um código de desenvolvimento frontend

Hoje em dia o GraphQL se tornou uma ferramenta que gera uma flexibilidade gigantesca na criação de rotas de APIs (justamente por não as ter), o que deixa ainda mais performático com outras ferramentas que criam toda a lógica do GraphQL para você, tornando a criação de um backend para MVPs extremamente simples e rápida. Vamos explorar como tornar o nosso frontend tão performático quanto, automatizando toda a geração de hooks e funções para chamadas server e client side.

Setup projeto em Next

Primeiramente, criaremos um projeto em Next, pois o intuito é automatizarmos tanto as lógicas de chamas client side quanto server side, e para isso, executamos o comando abaixo:

yarn create next-app graphql-next --typescript

Setup Apollo

Agora, vamos instalar tanto o GraphQL, quanto o Apollo Client na nossa aplicação:

yarn add graphql@15 @apollo/client

Instalamos o GraphQL na versão 15, pois o Apollo e o Codegen ainda não suportam as versões mais novas.

Nos meus projetos next, costumo colocar todas as pastas dentro da pasta src, com exceção da pasta public. Dentro da nossa src, vamos criar uma pasta “lib”, onde criaremos um arquivo “apollo.ts”. Nesse arquivo, vamos criar as configurações do client do Apollo:

// src/lib/apollo.ts
import { ApolloClient, createHttpLink, from, InMemoryCache } from "@apollo/client"

const httpLink = createHttpLink({
  uri: '<https://rickandmortyapi.com/graphql>',
  fetch
})

const cache = new InMemoryCache()

export const apolloClient = new ApolloClient({
  link: from([httpLink]),
  cache
})

Como exemplo, utilizaremos a API do Rick e Morty, que pode ser acessada pelo link abaixo:

Playground – https://rickandmortyapi.com/graphql

Para que o Apollo funcione em qualquer lugar da nossa aplicação, precisamos colocar o nosso ApolloProvider no nosso _app.tsx, e passar o apolloClient que acabamos de criar, da seguinte forma:

// pages/_app.tsx
import type { AppProps } from 'next/app'
import { ApolloProvider } from '@apollo/client'
import { apolloClient } from '../lib/apollo'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ApolloProvider client={apolloClient}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp

Agora, dentro da pasta pages, vamos criar uma query no nosso index.tsx apenas para testar a implementação do Apollo:

// pages/index.tsx
import { gql, useQuery } from '@apollo/client'
import type { NextPage } from 'next'

const GET_CHARACTERS = gql`
  query {
    characters {
      results {
        name
      }
    }
  }
`

const Home: NextPage = () => {
  const { data } = useQuery(GET_CHARACTERS)
  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

export default Home

Note que no modelo tradicional do Apollo, utilizamos o hook useQuery passando a nossa query. A ideia é que de acordo com schema do nosso GraphQL, todos os hooks com cada uma dessas queries sejam gerados automaticamente, tanto para serem utilizadas no client side quanto no server side.

Dessa forma teremos o seguinte retorno na nossa página inicial:

Codegen

Chegou a hora de ver a mágica acontecer! Como falamos anteriormente, o Codegen criará hooks usando o useQuery que utilizamos anteriormente. Mas, já passando todas as queries prontas e, o melhor de tudo, tipadas!

Para isso, vamos instalar alguns pacotes:

yarn add @graphql-codegen/cli @graphql-codegen/import-types-preset @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo graphql-codegen-apollo-next-ssr -D

Sim, são vários pacotes, mas cada um tem seu papel para fazer toda a implementação com Typescript, Next e SSR.

Agora, vamos criar o seguinte arquivo yml na raiz do nosso projeto, que é o arquivo que recebe todas as configurações do codegen:

overwrite: true

schema: '<https://rickandmortyapi.com/graphql>'

documents: './src/graphql/**/*.graphql'

generates:
  src/graphql/generated/graphql.tsx:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-apollo'
    config:
      reactApolloVersion: 3
      withHooks: true
      withHOC: false
      withComponent: false
      exportFragmentSpreadSubTypes: true
      documentMode: graphQLTag
  src/graphql/generated/page.tsx:
    config:
      documentMode: external
      importDocumentNodeExternallyFrom: ./graphql
      reactApolloVersion: 3
      withHooks: true
      contextType: 'ApolloClientContext'
      contextTypeRequired: true
      apolloClientInstanceImport: '../../lib/withApollo'
    preset: import-types
    presetConfig:
      typesPath: ./graphql
    plugins:
      - 'graphql-codegen-apollo-next-ssr'

Esse arquivo basicamente configura todos os plugins que instalamos anteriormente, indica onde estão as queries que iremos criar, onde está o nosso cliente e qual o endereço do nosso schema graphql.

Vamos criar um script no nosso package.json para rodar esse arquivo:

// package.json
[...]
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "codegen": "graphql-codegen --config codegen.yml"
  },
[...]

E vamos recriar nossa query, agora separada em um arquivo “.graphql”:

// src/graphql/queries/**characters.graphql**
query GetCharacters {
    characters {
      results {
        name
      }
    }
  }

Por fim, executamos o nosso script para gerar os nossos hooks, funções e toda a tipagem:

yarn codegen

Então será gerado as seguintes pastas e arquivos no seu projeto:

Agora vamos testar nossos novos hooks. Vamos voltar ao nosso index.tsx, deletar nossa query e o useQuery, e utilizar o nosso useGetCharactersQuery:

// index.tsx
import type { NextPage } from 'next'
import { useGetCharactersQuery } from '../graphql/generated/graphql'

const Home: NextPage = () => {
  const { data } = useGetCharactersQuery()

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

export default Home

Se tudo estiver funcionando, você verá que nada mudou e que a nossa query continua funcionando normalmente. Vale ressaltar que agora o nosso data está todo tipado e já sabemos exatamente a estrutura que virá do nossa query.

SSR

Para utilizar as nossas queries no lado do servidor, vamos precisar fazer algumas alterações no nosso client do Apollo e transformar nosso provider em um HOC (High Order Component). Ou seja, iremos criar um componente que recebe outro componente e encapsula esse componente no nosso ApolloProvider.

Basicamente, transformaremos o nosso apolloClient nesse HOC e renomearemos para withApollo:

// src/lib/withApollo.tsx
import {
  ApolloClient,
  ApolloProvider,
  createHttpLink,
  from,
  InMemoryCache,
  NormalizedCacheObject,
} from '@apollo/client'
import {  GetServerSidePropsContext, NextPage } from 'next'

export const withApollo = (Component: NextPage) => {
  return function Provider(props: any) {
    return (
      <ApolloProvider client={getApolloClient(undefined, props.apolloState)}>
        <Component />
      </ApolloProvider>
    )
  }
}

export type ApolloClientContext = GetServerSidePropsContext

export function getApolloClient(
  ctx?: ApolloClientContext,
  ssrCache?: NormalizedCacheObject
) {
  const httpLink = createHttpLink({
    uri: '<https://rickandmortyapi.com/graphql>',
    fetch
  })
  
  const cache = new InMemoryCache().restore(ssrCache ?? {})
  
  return new ApolloClient({
    link: from([httpLink]),
    cache
  })  
}

Se você notar, vai ver que existem alguns detalhes a mais na implementação do withApollo, como um restore do cache e um contexto (ctx) que não é usado em lugar nenhum. Mas, isso são parâmetros que o próprio Apollo e o Codegen usam nas suas implementações. Sem o restore do cache, o SSR não funciona, pois o Apollo armazena as queries SSR nesse cache. E, sem esse ctx, o Codegen também implementa suas funções errado, já que nas funções ele utiliza tanto esse ctx, quanto a exportação que fizemos do tipo “ApolloClientContext”.

Agora, vamos remover o ApolloProvider do _app.tsx:

// _app.tsx
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
      <Component {...pageProps} />
  )
}

export default MyApp

E no nosso index, vamos encapsular a exportação do nosso componente com o withApollo:

// index.tsx
import type { NextPage } from 'next'
import { useGetCharactersQuery } from '../graphql/generated/graphql'
import { withApollo } from '../lib/withApollo'

const Home: NextPage = () => {
  const { data } = useGetCharactersQuery()

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

export default withApollo(Home)

Tudo estará funcionando normalmente.

Realizando nossa chamada SSR

Para realizarmos a nossa query pelo lado do servidor, basta apenas fazermos mais um encapsulamento no nosso HOC do withApollo e depois chamar uma função que gera as nossas props do SSR já com o nosso data:

// index.tsx
import type { GetServerSideProps, NextPage } from 'next'
import { GetCharactersQuery } from '../graphql/generated/graphql'
import { getServerPageGetCharacters, ssrGetCharacters } from '../graphql/generated/page'
import { withApollo } from '../lib/withApollo'

type HomeProps = {
  data: GetCharactersQuery
}

const Home: NextPage<HomeProps> = ({ data }) => {
  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  return getServerPageGetCharacters({}, ctx)
}

export default withApollo(ssrGetCharacters.withPage()(Home))

Assim, vamos receber dentro das nossas props o data com resultado da nossa query.

Conclusão

Dessa forma, basta apenas escrever as nossas queries uma por uma e colocar na nossa pasta, gerar todos os hooks e funções com o codegen, e ir utilizando tanto no client side, quanto no server side da nossa aplicação. Esse setup facilita bastante o desenvolvimento, principalmente porque não temos que ficar criando tipos e importando queries pra lá e pra cá, já que o codegen faz todo esse trabalho para nós.

Espero que tenham gostado. Para acessar o código dessa aplicação de exemplo, basta clicar aqui.

Nos vemos no próximo artigo!

Compartilhar:

Arthur
Último artigo
Processo de contratação em escala: confira dicas infalíveis para realizá-lo
Próximo artigo
O que comunidades, empregos, tatus e desenvolvedores têm em comum?