GraphQL: argumentos, coleções e relações
Evolua sua API GraphQL em Node.js para receber argumentos, trabalhar com coleções nos dois sentidos (servidor para cliente e cliente para servidor), filtrar resultados e modelar relações entre tipos.
1. Visão geral
Clientes de APIs GraphQL podem enviar argumentos com o intuito, por exemplo, de fazer cadastros, filtros etc. Além disso, também é possível lidar com coleções e especificar relações entre itens de interesse. Neste material, estudaremos estes três itens.
O que você vai aprender
- Declarar argumentos em queries e usá-los nos resolvers
- Os quatro parâmetros recebidos por todo resolver:
parent,args,ctxeinfo - Devolver coleções de escalares e de tipos customizados
- Receber coleções enviadas pelo cliente
- Filtrar resultados com argumentos
- Modelar uma relação entre tipos (usuários que possuem livros)
O que você vai precisar
- O projeto do codelab anterior ("GraphQL: introdução e primeira API")
- Node.js e um editor de código
2. Projeto da aula anterior
Abra o projeto da aula anterior para prosseguir com os testes deste material. Ele pode ser obtido em https://github.com/professorbossini/maua_20202_graphql (Link 2.1.1).
Para colocá-lo em execução, abra um terminal e use
Terminal
npm run start
3. Hello, argumentos
Começamos especificando uma nova query a fim de ilustrar o uso de argumentos. Ela devolve uma única String (de boas-vindas) incluindo um nome recebido como argumento. Para defini-la, usamos o operador parênteses. Note que o argumento pode ser opcional. Contudo, devemos especificar o tipo de dado esperado.
src/index.js · Listagem 2.2.1
const typeDefs = `
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
A seguir, definimos o seu resolver associado.
src/index.js · Listagem 2.2.2
const resolvers = {
Query: {
bemVindo() {
return "Bem vindo!"
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
No Playground (localhost:porta) você pode testar o novo endpoint com a query a seguir.
GraphQL Playground · Listagem 2.2.3
query{
bemVindo
}
Note, contudo, que não estamos utilizando o argumento nome especificado. Para tal, podemos ajustar a consulta como a seguir.
GraphQL Playground · Listagem 2.2.4
query{
bemVindo (nome: "Maria")
}
Os quatro parâmetros dos resolvers
Agora é necessário ajustar o resolver para que ele faça uso do argumento passado. Há quatro argumentos que são passados para todos os resolvers.
- parent: permite obter o objeto possuidor dos dados envolvidos (um usuário que possui muitos livros, por exemplo)
- args: contém os argumentos passados (é esse que vamos usar agora)
- ctx (de context): contém dados como o id de um usuário logado, por exemplo
- info: informações sobre o que foi enviado ao servidor (veremos em breve)
Faça o ajuste a seguir para verificar o que cada um deles é.
src/index.js · Listagem 2.2.5
const resolvers = {
Query: {
bemVindo(parent, args, ctx, info) {
console.log("parent: " + JSON.stringify(parent));
console.log("args: " + JSON.stringify(args));
console.log(ctx);
console.log("info: " + JSON.stringify(info));
return "Bem vindo!"
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
Para testar, execute a query novamente no Playground e verifique a saída no console.
Desta forma, podemos acessar o argumento enviado como uma propriedade de args. Veja o trecho a seguir, que substitui o resolver bemVindo.
src/index.js · Listagem 2.2.6 (resolver bemVindo)
bemVindo(parent, args, ctx, info) {
return `
Bem vindo ${args.nome ? args.nome : 'visitante'}
`;
},
Faça testes enviando e deixando de enviar o argumento nome.
4. Coleções do servidor para o cliente
Pode ser necessário enviar coleções do cliente para o servidor ou vice-versa. Com GraphQL, podemos usar coleções de escalares e também de tipos customizados.
Começamos definindo uma nova query que devolve uma coleção (obrigatória, mesmo que vazia). Especificamos, inclusive, o tipo contido na coleção. Definimos uma query para uma coleção de notas.
src/index.js · Listagem 2.3.1
const typeDefs = `
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
A seguir, como de costume, definimos um resolver que entrará em execução quando o cliente executar essa query.
src/index.js · Listagem 2.3.2
const resolvers = {
Query: {
notas(parent, args, ctx, info) {
return [10, 2, 7, 7, 8]
},
bemVindo(parent, args, ctx, info) {
return `
Bem vindo ${args.nome ? args.nome : 'visitante'}
`;
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
Para testar, execute a query a seguir no Playground.
GraphQL Playground · Listagem 2.3.3
query{
notas
}
5. Coleções do cliente para o servidor
Para ilustrar o envio de coleções do cliente para o servidor, vamos escrever um endpoint que soma uma quantidade arbitrária de valores. Começamos especificando a query.
src/index.js · Listagem 2.3.4
const typeDefs = `
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
adicionar (numeros: [Float!]!): Float!
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
O resolver é exibido a seguir.
src/index.js · Listagem 2.3.5
const resolvers = {
Query: {
adicionar(parent, args, ctx, info) {
return args.numeros.length === 0 ? 0 :
args.numeros.reduce((ac, atual) => {
return ac + atual;
})
},
notas(parent, args, ctx, info) {
return [10, 2, 7, 7, 8]
},
bemVindo(parent, args, ctx, info) {
return `
Bem vindo ${args.nome ? args.nome : 'visitante'}
`;
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
Para testar, execute a query a seguir no Playground.
GraphQL Playground · Listagem 2.3.6
query{
adicionar(numeros:[1, 2])
}
6. Coleções de tipos customizados e filtros
Também podemos lidar com coleções de tipos que nós mesmos definimos, como o tipo Livro. Para devolver uma coleção de livros, começamos definindo a query a seguir.
src/index.js · Listagem 2.3.7
const typeDefs = `
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
livros: [Livro!]!
adicionar (numeros: [Float!]!): Float!
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
A seguir, no começo do arquivo, fora do escopo de qualquer outra definição, defina um vetor de livros fictício.
src/index.js · Listagem 2.3.8
const livros = [
{
id: '1',
titulo: 'Effective Java',
genero: "Técnico",
edicao: 3,
preco: 39.99
},
{
id: '2',
titulo: "Concrete Mathematics",
genero: "Técnico",
edicao: 1,
preco: 89.99
}
];
O próximo passo é especificar um resolver condizente com a query especificada. Ele deve se chamar livros e devolver uma coleção de livros, como prometido.
src/index.js · Listagem 2.3.9
const resolvers = {
Query: {
livros (parent, args, ctx, info) {
return livros;
},
adicionar(parent, args, ctx, info) {
return args.numeros.length === 0 ? 0 :
args.numeros.reduce((ac, atual) => {
return ac + atual;
})
},
notas(parent, args, ctx, info) {
return [10, 2, 7, 7, 8]
},
bemVindo(parent, args, ctx, info) {
return `
Bem vindo ${args.nome ? args.nome : 'visitante'}
`;
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
A listagem a seguir mostra como testar no Playground. Note que estamos buscando uma coleção de livros e cada um deles é composto por várias partes. Precisamos especificar explicitamente as partes de interesse.
GraphQL Playground · Listagem 2.3.10
query{
livros {
id,
edicao,
titulo
}
}
Filtrando com um argumento
Podemos utilizar um argumento para filtrar o que é devolvido. Por exemplo, podemos especificar que somente desejamos os livros cujo preço seja menor ou igual a um valor específico. Para isso, ajuste a query como a seguir.
src/index.js · Listagem 2.3.11 (type Query)
type Query {
livros (precoMaximo: Float!): [Livro!]!
adicionar (numeros: [Float!]!): Float!
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
A seguir, use a função filter no resolver.
src/index.js · Listagem 2.3.12 (resolver livros)
livros(parent, args, ctx, info) {
return livros.filter((l) => {
return l.preco <= args.precoMaximo
});
},
Para testar, precisamos especificar o valor máximo desejado no Playground.
GraphQL Playground · Listagem 2.3.13
query{
livros (precoMaximo:40) {
id,
edicao,
titulo
}
}
7. Relações
É muito comum que existam relações entre tipos que definimos. Por exemplo, podemos dizer que um usuário é dono de muitos livros. Nesta seção veremos como GraphQL permite que lidemos com relações.
Começamos definindo um novo tipo para representar pessoas.
src/index.js · Listagem 2.4.1
const typeDefs = `
type Usuario{
id: ID!,
nome: String!,
idade: Int!,
livros: [Livro!]
},
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
livros (precoMaximo: Float!): [Livro!]!
adicionar (numeros: [Float!]!): Float!
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
A seguir, vamos definir uma coleção de usuários. Cada usuário, por sua vez, terá uma coleção de livros. A definição é feita fora de qualquer função, tal qual fizemos com a coleção de livros.
src/index.js · Listagem 2.4.2
const usuarios = [{
id: '100',
nome: 'Jose',
livros: [{
id: '1',
titulo: 'Effective Java',
genero: "Técnico",
edicao: 3,
preco: 39.99
},
{
id: '2',
titulo: "Concrete Mathematics",
genero: "Técnico",
edicao: 1,
preco: 89.99
}
]
}, {
id: '101',
nome: 'Maria',
livros: [{
id: '5',
titulo: 'Programming Challenges',
genero: "Técnico",
edicao: 1,
preco: 39.99
}]
}]
A nova query que dá acesso aos usuários é exibida a seguir.
src/index.js · Listagem 2.4.3
const typeDefs = `
type Usuario{
id: ID!,
nome: String!,
idade: Int!,
livros: [Livro!]
},
type Livro {
id: ID!
titulo: String!
genero: String!
edicao: Int,
preco: Float
},
type Query {
usuarios: [Usuario!]!
livros (precoMaximo: Float!): [Livro!]!
adicionar (numeros: [Float!]!): Float!
notas: [Int!]!
bemVindo (nome: String) : String!
effectiveJava: Livro!
}
`;
O resolver associado simplesmente devolve a coleção de usuários.
src/index.js · Listagem 2.4.4
const resolvers = {
Query: {
usuarios() {
return usuarios;
},
livros(parent, args, ctx, info) {
return livros.filter((l) => {
return l.preco <= args.precoMaximo
});
},
adicionar(parent, args, ctx, info) {
return args.numeros.length === 0 ? 0 :
args.numeros.reduce((ac, atual) => {
return ac + atual;
})
},
notas(parent, args, ctx, info) {
return [10, 2, 7, 7, 8]
},
bemVindo(parent, args, ctx, info) {
return `
Bem vindo ${args.nome ? args.nome : 'visitante'}
`;
},
effectiveJava() {
return {
id: '123456',
titulo: 'Effective Java',
genero: 'Técnico',
edicao: 3,
preco: 43.9
}
}
}
};
Para testar no Playground, precisamos especificar a query usuarios e, a seguir, especificar que desejamos os livros de cada usuário. Note que é necessário especificar cada propriedade desejada dos livros.
GraphQL Playground · Listagem 2.4.5
query{
usuarios{
nome
livros{
id,
titulo
}
}
}
8. Exercícios
- Crie queries/resolvers para fazer as quatro operações aritméticas básicas envolvendo dois argumentos enviados pelo cliente.
- Crie um endpoint que recebe uma coleção de valores numéricos e a devolve ordenada.
9. Encerramento
Você usou argumentos, coleções, filtros e relações em uma API GraphQL. No próximo codelab da série, as relações passam a ser resolvidas por resolvers próprios de cada tipo.
Referências
- Babel · The compiler for next generation JavaScript. 2020. Disponível em https://babeljs.io. Acesso em agosto de 2020.
- Node.js. 2020. Disponível em https://nodejs.org. Acesso em agosto de 2020.
- WILSON, Jim R. Node.js 8 the Right Way. 1st edition. The Pragmatic Programmers, LLC, 2018.