Graphql para leigos | Pt 2
Esse artigo é uma continuação do primeiro sobre o assunto. Vale dar uma lida antes de seguir :)
Aém dos tipos mais básicos que o Graphql exige para começar a entender o seu modelo, a mecânica dele está mesmo ligada a estruturas que ele usa para distinguir suas entradas e saídas. Segue o fio para um mais de conhecimento sobre o Graphql…
Queries
O ponto de entrada do graphql é definido por uma Query. A Query define os dados que devem ser dados como entrada para que a requisição seja realizada no backend. Essa é uma declaração necessária, e é com base nela que o graphql entende se uma requisição é válida ou não. Inserimos as Queries dentro da mesma variável dos tipos, a typeDef.
As Queries são usadas quando queremos consultar dados, como em uma requisição GET. No exemplo abaixo, vemos uma Query que, quando o usuário requisita na função usuariosLogados, retorna uma lista de objetos do tipo Usuario.
Temos um segundo exemplo que, na função horaAtual, retorna um objeto do tipo Date, definos nos tipos escalares.
const typeDefs = gql`
scalar Date
type Query{
usuariosLogados:[Usuario]
horaAtual: Date
}
`
Mutations
Se as Queries têm a função de prover uma função de consulta, as Mutations, por outra vez, são usadas para alterar dados.
Dessa forma, toda vez que quisermos inserir, alterar ou até deletar recursos, usamos o tipo Mutation para indicar esse tipo de operação. O funcionamento é o mesmo da Query, precisando ter um resolver específico, mas aqui tem uma semântica diferente.
Nesse exemplo, temos uma Mutation cujo objetivo é criar um novo recurso do tipo Usuario. Ela define os atributos de entrada (nome, salario, vip), e um retorno do mesmo tipo, que não pode ser nulo(!).
const typeDefs = gql`
type Mutation{
novoUsuario(
nome: String,
salario: Float,
vip: Boolean
): Usuario!
}
`
Resolvers
Como vimos, os resolvers existem com o intuito de traduzir os tipos definidos nos typeDefs. É com base neles que o Graphql entende quais são os tipos e objetos disponíveis para consulta na integração.
Por padrão, todas as consultas são inseridas em um resolver primário chamado Query. É dentro de Query que estarão disponíveis os modelos que podem ser resolvidos. Inicialmente, os tipos definidos devem ter os mesmos nomes inseridos no typeDefs. Veja como isso funciona, ao definimos um tipo ola e, em seguida, um resolver dentro da Query, usando uma função com o mesmo nome.
const typeDefs = gql`
type Query{
ola: String
}
`const resolvers = {
Query: {
ola() {
return “Bom dia!”
}
}
}
Caso seja necessário, é possível criar resolvers específicos para objetos criados dentro do seu contexto. Nesse exemplo abaixo, o objeto volta de uma fonte externa (pode ser um banco de dados) com um campo com nome diferente do determinado nos tipos. Na aplicação, o atributo é salario, mas na fonte, o retorno é salario_real.
Para resolver isso, criamos um resolver acima de Query, o Usuario, onde ele resolve esse atributo, atribuindo a salario o valor recebido em salario_real.
const typeDefs = gql`
scalar Date
type Usuario{
id: ID!
nome: String
salario: Float
vip: Boolean
}type Query{
usuarioLogado: Usuario
}
`const resolvers = {
Usuario: {
salario(usuario){
return usuario.salario_real
}
},
Query: {
usuarioLogado() {
return {
id: 1,
nome: “Fulano de tal”,
salario_real: 5240.20,
vip: false
}
}
}
}
Os resolvers também possibilitam o uso de argumentos, para personalizar a sua consulta. Com os argumentos, o retorno deixa de ser estático e o usuário pode indicar valores para parametrizar o retorno, de acordo com a lógica realizada.
Nesse exemplo, temos uma lista de perfis em uma variável, e definimos uma Query que consulta um perfil pelo ID. O resolver garante essa busca, aplicando um filtro em cima da constante, mas poderia ser em cima de um retorno de uma fonte externa.
const perfis = [
{ id: 1, nome: “Comum” },
{ id: 2, nome: “Administrador” }
]const typeDefs = gql`
type Perfil{
id: ID!
nome: String
}type Query{
perfil(id: ID): Perfil
}
`const resolvers = {
Query: {
perfil(_, { id }){
return perfis.find(p => p.id == id)
}
}
}
É possível também integrar resolvers, tendo um resolver dentro de outro e normalizar o modelo de dados. Um usuário, por exemplo, tem um perfil, que pode estar em um resolver diferente. Assim, basta o usuário referenciar o id do perfil, que o graphql monta essa composição.
const users = [
{ id: 1, nome: “Fulano de tal”, perfil_id: 1 },
{ id: 2, nome: “Beltrano Silva”, perfil_id: 2 },
{ id: 3, nome: “Ciclano Pereira”, perfil_id: 1 }
]const perfis = [
{ id: 1, nome: “Comum” },
{ id: 2, nome: “Administrador” }
]const typeDefs = gql`
type Usuario{
id: Int
nome: String
perfil: Perfil
}type Perfil{
id: ID!
nome: String
}type Query{
usuariosLogados: [Usuario]
}
`const resolvers = {
Usuario: {
perfil(usuario){
return perfis.find(p => p.id == usuario.id)
}
},Query: {
usuariosLogados() {
return users
},
}
}
O Graphql é uma ferramenta que resolve um problema bastante específico, mas tem um poder grande de facilitar a leitura do seu código. Vale a pena dedicar um tempo para imergir nesse conhecimento.
Espero ter agregado um pouco com o seu conhecimento e na resolução de algum problema do seu dia a dia.
E você, usa Graphql? O que acha da ferramenta?
Indicações adicionais:
Curso na Udemy sobre o assunto: https://www.udemy.com/course/graphql-criando-apis-profissionais-e-flexiveis/
Documentação da fermementa(em inglês): https://graphql.org