Fullstack com Node.js, React e GraphQL - 6: Servidor GraphQL
E aí, pessoal! Neste sexto post da série Fullstack com Node.js, React e GraphQL vamos adicionar o GraphQL no servidor usando Apollo Server.
GraphQL
GraphQL é uma linguagem de consulta (query language) para APIs que permite ao cliente descrever exatamente os dados que quer receber, e também um conjunto de tecnologias que permite ao servidor descrever os dados disponíveis e entregar estes dados, que podem ser buscados de múltiplas fontes (bancos de dados, APIs REST, outros servidores GraphQL).
Para ilustrar, veja um exemplo de query e o retorno do servidor:
Eu pedi pelas corretoras, campos id e nome, e dentro de cada corretora trazer seus investimentos, apenas campo nome. Esta é a parte QL do nome (query language).
E o grafo?
A parte Graph do nome vem do fato que toda busca percorre um grafo.
Grafo é uma estrutura formada por vértices (ou pontos ou nós) e arestas (ou linhas) que ligam os vértices.
Para ilustrar, veja como ficaria o grafo que representa nossa aplicação. Destacado em laranja está o caminho percorrido pela query acima.
Grafo com a representação de nossos modelos e suas relações.
No GraphQL sempre temos um nó especial, chamado Query (ou ponto de entrada, root query, entrypoint, root type) que é por onde toda consulta deve iniciar.
No nosso exemplo, a partir daí a consulta pode requisitar um ou mais Brokers ou Investments, e sequencialmente ir seguindo as arestas e pedindo dados de outros modelos relacionados. Essa estrutura, e o que pode ou não ser pedido será definida por nós, quando criarmos nosso schema.
Apollo
Apollo é uma plataforma para desenvolvimento com GraphQL composta por:
- Apollo Client: facilita a integração do frontend com o servidor GraphQL, possuindo bibliotecas para os principais frameworks JS (React, Vue, Angular) e plataformas mobile nativas (Android e iOS).
- Apollo Engine: fornece ferramental auxiliar para infraestrutura como caching, tratamento de erros e rastreamento de performance.
- Apollo Server: bibliotecas que auxiliam na criação do servidor. Vamos usá-lo nesta parte do projeto.
Schema
A primeira coisa que precisamos pensar é na forma dos dados que serão disponibilizados. Como já temos nossos modelos definidos, vamos expor o que poderá ser consultado através do GraphQL (montar aquele grafo que vimos acima).
O schema é como se fosse um contrato entre o fornecedor e o consumidor dos dados (ou entre o server e o client).
Ponto de entrada
Vamos criar nosso schema em src/graphql/schema.graphql, começando pela root query:
type Query {
brokers(limit: Int): [Broker]
broker(id: ID!): Broker
investments(limit: Int): [Investment]
investment(id: ID!): Investment
}
Cada atributo possui um nome e um tipo de retorno e, opcionalmente, pode receber parâmetros. A sintaxe se assemelha a assinatura de funções em linguagens fortemente tipadas.
A !
como em id: ID!
significa obrigatoriedade daquele valor.
[]
como em [Broker]
significa uma lista de objetos daquele tipo.
Resumindo, nosso ponto de entrada permite buscar por um broker
ou um investment
, informando o id como parâmetro, ou por uma lista de brokers
ou investments
, opcionalmente informando um limite.
Demais nós
Na sequência descrevemos os nós que representam nossos modelos, ficando assim:
scalar Date
type Query {
brokers(limit: Int): [Broker]
broker(id: ID!): Broker
investments(limit: Int): [Investment]
investment(id: ID!): Investment
}
type Broker {
id: ID!
name: String!
investments: [Investment]
}
type Investment {
id: ID!
name: String!
broker: Broker
balanceUpdates(limit: Int, order: [[String]]): [BalanceUpdate]
transactions: [Transaction]
}
type BalanceUpdate {
id: ID!
amount: Float!
date: Date!
}
type Transaction {
id: ID!
amount: Float!
date: Date!
}
Para mais detalhes, veja a documentação sobre a definição de schema: http://graphql.org/learn/schema/
Resolvers
Definido o schema e a forma do nosso grafo, a aplicação ainda não sabe como buscar os dados para atender as queries. Vamos resolver isso com resolvers. 🥁😁
Vamos criar o arquivo src/graphql/resolvers.js:
const { GraphQLString } = require('graphql')
const { Broker, Investment, BalanceUpdate, Transaction } = require('../models')
module.exports = {
Query: {
brokers: (obj, args) => Broker.all(args),
broker: (obj, { id }) => Broker.findById(id),
investments: (obj, args) => Investment.all(args),
investment: (obj, { id }) => Investment.findById(id),
},
Investment: {
broker: (obj) => Broker.findOne({ where: { id: obj.BrokerId } }),
balanceUpdates: (obj, args) =>
BalanceUpdate.all({ where: { InvestmentId: obj.id }, ...args }),
transactions: (obj) => Transaction.all({ where: { InvestmentId: obj.id } }),
},
Broker: {
investments: (obj) => Investment.findAll({ where: { BrokerId: obj.id } }),
},
Date: GraphQLString,
}
Este arquivo exporta um objeto JS onde para cada navegação de um nó a outro do grafo definimos uma função que irá buscar os dados referente àquela parte da query (E esta busca pode ser em mútiplos locais: banco de dados, API REST ou até outro servidor GraphQL).
Por exemplo, se estou em Investimento e quero buscar a Corretora daquele investimento, tenho:
Investment: {
broker: (obj) => Broker.findOne({ where: { id: obj.BrokerId } })
}
O primeiro argumento da função, obj
, é o objeto que representa aquele nó. Assim, para buscar o Broker usamos Broker.findOne
passando o atributo BrokerId
do Investment.
Para queries que aceitam parâmetros, estes são passados no segundo argumento da função. Exemplo:
Query: {
brokers: (obj, args) => Broker.all(args),
broker: (obj, { id }) => Broker.findById(id)
}
Endpoint de consultas
Temos o schema, que descreve as queries, e os resolvers que buscam os dados. Vamos juntar as duas partes e criar nosso endpoint de consultas, que é o endereço onde o frontend da aplicação vai bater para trazer os dados para a tela. Aqui vamos usar o Apollo Server:
const { graphqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const { importSchema } = require('graphql-import')
const resolvers = require('./graphql/resolvers')
const setup = (app) => {
const schema = makeExecutableSchema({
typeDefs: importSchema('src/graphql/schema.graphql'),
resolvers,
})
// graphql endpoint
app.use('/graphql', graphqlExpress({ schema }))
}
module.exports = setup
Usei a função makeExecutableSchema
do módulo graphql-tools para criar um schema “executável” a partir do nosso schema e resolvers.
Para importar o arquivo schema.graphql usei o módulo graphql-import, já que não é um arquivo JS e não pode ser importado diretamente.
Com o schema “executável”, uso a função graphqlExpress
, do módulo apollo-server-express, que é um middleware do Express, para definir o endpoint no caminho /graphql.
Para organização eu deixei este código em um arquivo separado, setupGraphQL.js, que exporta uma função de setup, que é usada no src/index.js esta forma:
const setupGraphQL = require('./setupGraphQL')
const app = express()
setupGraphQL(app)
Agora precisamos testar nossas consultas. Ah, se tivesse uma espécie de playground onde pudéssemos inserir queries e ver os resultados…
Mas tem! É o…
GraphiQL
O GraphiQL é uma interface web que permite inserir queries, possui autocomplete e uma documentação com todas as queries e campos possíveis. Tudo gerado a partir do nosso schema.
Interface do GraphiQL
Para habilitar esta interface basta usar o middleware graphiqlExpress
do Apollo, informando o endpoint de consultas.
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))
Assim, basta rodar o servidor local e acessar http://localhost:5000/graphiql para explorar seu servidor GraphQL.
Resultado final
O código do projeto até este ponto está em: https://github.com/doug2k1/my-money/tree/v5.0.0
No próximo capítulo
Na próxima parte vamos implementar autenticação para proteger nosso endpoint de consultas contras bisbilhoteiros.
Stay tuned!
Feedbacks?
E aí, o que está achando até agora? Algo que precisa melhorar?
Tags: full-stack|graphql|nodejs|react