Criando um servidor GraphQL com o Apollo

christoffer noring Blocked Desbloquear Seguir Seguindo 2 de janeiro

Para criar um servidor GraphQL usando o Apollo Server, precisamos fazer três coisas:

  • instalar o apollo-server e o graphql
  • definir um esquema
  • definir resolvedores

Vamos instalar as dependências necessárias:

 npm install --save apollo-server graphql 

Uma vez feito isso, podemos prosseguir com os próximos passos.

Definir um esquema

Para definir um esquema, precisamos estar usando uma função chamada gql que nos permite definir um esquema como uma string de modelo de múltiplas linhas e, o mais importante, usando a linguagem de consulta GraphQL.

Primeiro, nós importamos o gql :

 const {gql} = require ("apollo-server"); 

Em seguida, podemos começar a definir o esquema em si:

 const typeDefs = gql` 
 tipo Consulta { 
 produtos: [Produto] 
 produto (id: Int!): Produto 
 } 
 tipo Product { 
 eu não fiz, 
 nome: String, 
 descrição: String 
 } 
 input ProductInput { 
 nome: String, 
 descrição: String 
 } 
 type Mutation { 
 login (email: String): String # token 
 createProduct (produto: ProductInput) 
 } 
`

Finalmente, exportamos o esquema para uso posterior

 module.exports = typeDefs; 

O arquivo completo agora deve ficar assim:

 const {gql} = require ("apollo-server"); 
 const typeDefs = gql` 
 tipo Consulta { 
 produtos: [Produto] 
 produto (id: Int!): Produto 
 } 
 tipo Product { 
 eu não fiz, 
 nome: String, 
 descrição: String 
 } 
 input ProductInput { 
 nome: String, 
 descrição: String 
 } 
 type Mutation { 
 login (email: String!): String # token 
 createProduct (produto: ProductInput): String 
 } 
`
 module.exports = typeDefs; 

Definir resolvedores

Em seguida, precisamos definir o que as seguintes consultas e mutações devem fazer:

  • produtos , isso deve retornar uma lista de produtos
  • produto (id: Int!) , isso deve retornar um produto
  • login (email: String) , isso deve logar no usuário
  • createProduct (produto: ProductInput) , isso deve criar um produto

Vamos começar um novo arquivo resolvers.js . Neste ponto, manteremos as coisas simples e teremos alguns dados estáticos locais, mas poderemos melhorar esse servidor fazendo com que ele chame um banco de dados ou uma API. Então, vamos definir alguns dados estáticos no topo:

 produtos const = []; 

Agora que o servidor Apollo espera um certo tipo de objeto de volta com a seguinte forma:

 module.exports = { 
 Inquerir: {}, 
 Mutação: {} 
 }; 

Não as duas palavras-chave Query e Mutation acima. Esses dois são objetos e precisamos definir cada função de resolução correspondente. Vamos começar adicionando uma função de resolvedor para produtos:

 const getProducts = () => { 
 return Promise.resolve (produtos); 
 } 
 module.exports = { 
 Inquerir: { 
 produtos: async () => getProducts 
 } 
 Mutação: {} 
 }; 

Um comentário no método getProducts() é que retornamos um Promise . Agora, poderíamos retornar nossa lista de products diretamente, mas isso é apenas uma maneira de mostrar que podemos retornar uma resposta assíncrona também, o que significa que isso se ajustará bem a nós falando com uma API de terceiros ou fazendo uma pesquisa em um banco de dados. Para acompanhar nossa resposta Promise , marcamos com async em nosso objeto de retorno.

Definir uma função de resolver com um parâmetro

Aprendemos como definir uma função de resolução, e agora vamos ver como podemos pegar um parâmetro da solicitação do usuário e consultar nosso backend com base nele.

 const getProductById = ({productId}) => { 
 return Promise.resolve (product.find (p => p.id === productId)); 
 } 
 module.exports = { 
 Inquerir: { 
 produtos: async () => getProducts, 
 produto: async (_, {id}) => getProductById ({productId: id}) 
 } 
 }; 

Acima, adicionamos o product ao nosso objeto Query e, como você pode ver, é possível extrair facilmente o id da solicitação do usuário.

Definir uma função de resolvedor para uma mutação

Para definir resolvedores de mutações, precisamos introduzir uma propriedade de Mutation em nosso objeto exportado, assim como precisávamos introduzir a Query . Então agora nosso código deve ficar assim:

 module.exports = { 
 Inquerir: { 
 produtos: async () => getProducts, 
 produto: async (_, {id}) => getProductById ({productId: id}) 
 } 
 Mutação: {} 
 }; 

Observe a adição de Mutation acima.

O próximo passo é adicionar nossa propriedade createProduct e uma função de resolvedor que lida com isso, da seguinte forma:

 const createProduct = ({product}) => { 
 const newId = products.length === 0? 1: produtos [produtos.length-1] .id + 1; 
 produtos = [… produtos, {… product, id: newId}]; 
 return Promise.resolve ('sucesso') 
 } 
 module.exports = { 
 Inquerir: { 
 produtos: async () => getProducts, 
 produto: async (_, {id}) => getProductById ({productId: id}) 
 } 
 Mutação: { 
 createProduct: async (_, {product}) => criarProduto ({product}) 
 } 
 }; 

Colocando tudo junto

Agora nós definimos um conjunto de funções de resolução, vamos dar uma olhada no código resultante em sua totalidade:

 produtos const = []; 
 const getProducts = () => { 
 return Promise.resolve (produtos); 
 } 
 const getProductById = ({productId}) => { 
 return Promise.resolve (product.find (p => p.id === productId)); 
 } 
 const createProduct = ({product}) => { 
 const newId = products.length === 0? 1: produtos [produtos.length-1] .id + 1; 
 produtos = [… produtos, {… product, id: newId}]; 
 return Promise.resolve ('sucesso') 
 } 
 module.exports = { 
 Inquerir: { 
 produtos: async () => getProducts, 
 produto: async (_, {id}) => getProductById ({productId: id}) 
 } 
 Mutação: { 
 createProduct: async (_, {product}) => criarProduto ({product}) 
 } 
 }; 

Como você pode ver, só operamos dados estáticos, mas podemos alternar facilmente essas chamadas para segmentar um banco de dados ou uma API de terceiros. Isso é totalmente sua responsabilidade. Desde que você se lembre de usar a palavra-chave await em sua definição de resolvedor, você é bom em interagir com qualquer coisa, retornar uma Promise .

Inicie o servidor

Nós definimos um esquema e um conjunto de funções de resolução. Agora vamos importar schema.js e resolvers.js e iniciar nosso servidor:

 const {ApolloServer} = require ("apollo-server"); 
 const typeDefs = require ("./ schema"); 
 resolvedores const = require ("./ resolvers"); 
 servidor const = novo ApolloServer ({typeDefs, resolvers}); 

 server.listen (). então (({url}) => { 
 console.log (`? Servidor pronto em $ {url}`); 
 }); 

Depurar

Navegue para http://localhost:4000

você deveria ter algo parecido com isto:

Como você pode ver, isso se parece muito com o ambiente GraphiQL que apresentamos no capítulo GraphQL + Node.js Express . A ideia é a mesma. À sua esquerda, você tem uma área onde você pode inserir queries e mutations e à sua direita é a área de resultados. No canto inferior esquerdo está uma área de variables que permite especificar variáveis de entrada que você pode usar no painel superior esquerdo.

Exemplo de uso

Vamos tentar criar uma query . Podemos fazer isso de duas maneiras:

  • usando a versão sem palavra-chave, {}
  • usando a palavra-chave query

A primeira variante é assim:

Como você pode ver acima, estamos usando apenas {} para especificar nossa consulta e, em seguida, consultamos launches recursos e detalhamos o recurso para obter as colunas de que precisamos, ou seja, mission e mission pegamos o name da coluna.

Vamos tentar usar a outra variante de consulta em seguida usando a query palavra-chave. A consulta da palavra-chave nos permite:

  • Nomeie a consulta para que possamos nos referir a ela mais tarde
  • Especifique mais de uma consulta na mesma janela, para que possamos escolher qual deles queremos em um determinado ponto no tempo
  • Especifique um parâmetro para a nossa consulta (vamos mostrar este no nosso próximo exemplo)

Acima, conseguimos especificar duas consultas diferentes e fornecemos o nome LaunchesNameOnly e LaunchesAll . Ao especificar a consulta LaunchesAll você pode ver como nosso ambiente nos ajuda, indicando o tipo de isBooked , que estamos prestes a selecionar. O tipo é um Boolean! , um Boolean obrigatório.

Em nosso próximo exemplo, mostramos como você aperta o botão grande de play e pode escolher entre diferentes consultas, como você pode ver, é realmente uma ótima idéia definir suas consultas com a query palavras-chave para criar um pedido em seu ambiente de teste:

Abaixo está simplesmente o resultado da execução da nossa consulta

Vamos tentar usar as variáveis a seguir e ver como podemos declarar aquelas na área inferior esquerda e usá-las em nossas queries e mutations .

Acima nós definimos OneLaunch que usa um parâmetro $id . Depois disso, esse parâmetro $id é passado para nossa expressão de consulta real da seguinte forma:

 launch (id: $ id) { 
 missão { 
 nome 
 } 
 } 

Agora, estamos prontos para invocar nossa consulta, com um parâmetro e simplesmente pressionamos o botão play e percebemos isso:

A razão pela qual obtemos o que foi dito acima é que a leitura do canto inferior esquerdo é assim:

As variáveis nesta área são todas feitas de um objeto e nós simplesmente adicionamos chaves a este objeto quando precisamos de um novo parâmetro da seguinte forma:

 { 
 "id": 1, 
 "otherparam": "algum valor" 
 } 

Consultas mais profundas

Um dos grandes pontos de venda do GraphQL é a capacidade de consultar profundamente uma hierarquia, da seguinte forma:

 { 
 orders { 
 criado, 
 criado por { 
 nome, 
 criado 
 } 
 Unid { 
 produtos { 
 nome 
 } 
 preço 
 } 
 } 
 } 

Acima, estamos consultando os orders recursos e, como você pode ver, podemos executar muitas subconsultas e consultar orders->items->product . Se fôssemos fazer isso com SQL, seria muito difícil com muitos JOINS . Então, como podemos construir isso no GraphQL?

Em suma, precisamos fazer duas coisas:

  • definir um resolvedor para o subtipo solicitado
  • armazenar os dados em um formato resolúvel . Isso significa que precisamos armazenar os dados como id/ids em vez do objeto completo

Armazene os dados como id / ids

Precisamos armazenar um pedido como este:

 ordens const = [{ 
 id: 1, 
 itens: [1,2,3] 
 }] 

Definir um resolvedor para um subtipo

Como isso é feito sintaticamente depende da implementação do servidor GraphQL que estamos lidando. Nesse caso, estamos lidando com a Apollo, então vamos ver como a Apollo a manipula. Sabemos que quando definimos um objeto de resolução no Apollo, é assim:

 { 
 Inquerir: { 
 pedidos: async () => getOrders (), 
 order: async (_, {id}) => getOrder (id) 
 } 
 Mutação: { 
 } 
 } 

Agora, nossa consulta de orders significa que estamos interessados em consultar todas as suas colunas, como created mas também por uma coluna complexa, como items e até mesmo items -> product para que possamos fazer uma consulta como esta:

 { 
 orders { 
 criado, 
 Unid { 
 produtos { 
 nome 
 } 
 preço 
 } 
 } 
 } 

Então, como resolvemos items ? Bem, vamos dar uma olhada em nossa definição de esquema de uma ordem:

 escreva Order { 
 data criada; 
 itens: [item] 
 } 

Com isso em mente, vamos voltar ao nosso objeto resolvedor e agora vamos adicionar um tipo de Order para gostar:

 { 
 Inquerir: { 
 pedidos: async () => getOrders (), 
 order: async (_, {id}) => getOrder (id) 
 } 
 Mutação: { 
 } 
 Ordem { 
 items: ({items}) => getItems (itens) 
 } 
 } 

Vamos ampliar nossa adição:

 Ordem { 
 items: ({items}) => getItems (itens) 
 } 

Aqui podemos ver que estamos resolvendo o que acontece se alguém perguntar por items . Nós estamos perdendo o método getItems() , então vamos definir isso:

 const itemsList = [{ 
 id: 1, 
 nome: 'item' 
 } 
 { 
 id: 2, 
 nome: 'item2' 
 }] 

const getItems = (itens) => {
 return Promise.resolve (itemsList) 
 } 

Ok, então isso lida com items então como lidamos com items->product . Bem, esse é bem simples, nós apenas olhamos novamente para o nosso esquema:

 tipo OrderItem { 
 preço: duplo; 
 produto: Produto; 
 } 

Nós vemos agora que precisamos adicionar OrderItem ao nosso objeto resolvedor, assim:

 { 
 Inquerir: { 
 pedidos: async () => getOrders (), 
 order: async (_, {id}) => getOrder (id) 
 } 
 Mutação: { 
 } 
 Ordem { 
 items: ({items}) => getItems (itens) 
 } 
 OrderItem { 
 produto: ({product}) => getProduct (produto) 
 } 
 } 

Resumo

Nós cobrimos

  • Instalando apollo-server e graphql , para ter as dependências necessárias para criar um servidor apollo.
  • Definiu um schema e um conjunto de resolvers .
  • Inicializei nosso servidor
  • Explorou o ambiente visual que nos permite executar consultas ou mutações

Seguindo em frente, veremos maneiras diferentes de consumir o servidor usando o apollo-client portanto, fique atento a isso.