Tenho ouvido muitos rumores sobre o trabalho com GraphQL e NoSQL, internamente no Couchbase e externamente. Como esse parece ser um assunto muito discutido, pensei em passar algum tempo aprendendo sobre ele para ver como poderia ser útil na criação de aplicativos da Web.
Vindo do mundo das APIs RESTful, o GraphQL é um conceito totalmente diferente para mim, embora tenha como objetivo resolver um problema semelhante de acesso e alteração de dados por meio de aplicativos voltados para o cliente. Felizmente, o Node.js e os bancos de dados funcionam bem juntos, portanto, no exemplo de banco de dados GraphQL abaixo, usaremos usando Node.js com Couchbase como nosso Banco de dados NoSQL camada.
No momento, não sou um especialista em GraphQL. Venho desenvolvendo APIs RESTful com estruturas como Express e Hapi há muitos anos. Dito isso, vou tentar explicar como aprendi sobre elas.
As APIs funcionam muito bem, mas quando se trata de obter as informações de que você precisa delas, as coisas podem ser feitas melhor. Por exemplo, e se você precisar de um monte de dados não relacionados ou pouco relacionados para o seu cliente? Você cria um endpoint enorme que retorna muitos dados ou faz uma solicitação para cada endpoint de API que possa conter seus dados? E se você precisar de apenas um fragmento dos dados retornados de um determinado endpoint?
É aqui que o GraphQL entra em ação. Por meio da linguagem de consulta, você pode especificar exatamente quais dados deseja que sejam retornados ao cliente em um determinado momento, sem precisar escrever um endpoint para tudo.
Criação de um novo aplicativo Node.js com o Express Framework
Usaremos o Node.js para criar uma interface para usar o Consultas GraphQL. Vamos começar criando um novo projeto, obtendo as dependências e inicializando nosso projeto em preparação para alguma lógica real.
Com o Node.js instalado, na linha de comando, execute:
|
1 2 3 4 5 6 |
npm init -y npm install couchbase --save npm install express --save npm install express-graphql --save npm install graphql --save npm install uuid --save |
Os comandos acima inicializarão um novo projeto criando um arquivo package.json e instalando as dependências necessárias. Das dependências, estamos instalando o Couchbase e um pacote para gerar cadeias de caracteres UUID que mais tarde representarão nossas chaves de documento. Os outros pacotes estão relacionados ao GraphQL e à vinculação ao Express Framework.
Crie um arquivo chamado app.js em seu projeto e inclua o seguinte código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const Express = require("express"); const Couchbase = require("couchbase"); const ExpressGraphQL = require("express-graphql"); const BuildSchema = require("graphql").buildSchema; const UUID = require("uuid"); var schema = BuildSchema(``); var resolvers = { }; var app = Express(); app.use("/graphql", ExpressGraphQL({ schema: schema, rootValue: resolvers, graphiql: true })); app.listen(3000, () => { console.log("Listening at :3000"); }); |
Ainda não vamos nos preocupar em preencher nosso esquema e configurar nosso banco de dados. No código acima, observe que importamos cada uma de nossas dependências e inicializamos o Express. Também definimos um único ponto de extremidade de API, que é como vamos fazer com que o Couchbase e o GraphQL interajam. Essencialmente, emitiremos todas as consultas GraphQL para esse único endpoint e ele responderá com os dados corretos com base em nosso esquema e resolvedores.
Definição de um esquema e resolvedores GraphQL
Agora que já temos a base do aplicativo, vamos definir um esquema e os resolvedores para realmente vincular os dados a possíveis consultas e tipos de dados.
Para este exemplo, vamos criar um aplicativo simples de blog. Por esse motivo, pode fazer sentido ter os seguintes modelos de esquema:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var schema = BuildSchema(` type Account { id: String, firstname: String, lastname: String, } type Blog { id: String, account: String!, title: String, content: String } `); |
Sabemos que teremos contas e que teremos artigos de blog para uma determinada conta. O ponto de exclamação em qualquer tipo de dados significa que se trata de uma propriedade obrigatória.
Agora precisamos conectar nosso modelo ao Couchbase para que possamos consultar os dados ou até mesmo criar dados.
Consulta e mutação de dados do Couchbase Server, o banco de dados NoSQL
Vamos supor que você já tenha Couchbase em execução. Não há nenhum requisito especial de configuração do Couchbase para este exemplo específico.
A primeira etapa é conectar-se ao Couchbase Server a partir do aplicativo Node.js. Na seção app.js adicione as seguintes linhas:
|
1 2 3 |
var cluster = new Couchbase.Cluster("couchbase://localhost"); cluster.authenticate("example", "123456"); var bucket = cluster.openBucket("example"); |
As linhas acima pressupõem que o Node.js e o banco de dados - Couchbase, neste exemplo - estejam sendo executados no computador local. Vamos usar um Bucket chamado exemplo e uma conta de usuário RBAC chamada exemplo.
Os modelos de dados para o GraphQL já estão em vigor, mas ainda não definimos as possíveis consultas ou mutações. Vamos modificar o esquema em nosso aplicativo para que se pareça com o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var schema = BuildSchema(` type Query { account(id: String!): Account, accounts: [Account], blog(id: String!): Blog, blogs(account: String!): [Blog] } type Account { id: String, firstname: String, lastname: String, } type Blog { id: String, account: String!, title: String, content: String } type Mutation { createAccount(firstname: String!, lastname: String!): Account createBlog(account: String!, title: String!, content: String!): Blog } `); |
Tanto para as consultas quanto para as mutações, serão chamadas funções especiais de resolução que fazem o trabalho pesado. Como resultado, um Conta ou um Blog será devolvido.
Começando com as mutações, estamos esperando um createAccount que espera dois parâmetros. Ela pode se parecer com o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { createAccount: (data) => { var id = UUID.v4(); data.type = "account"; return new Promise((resolve, reject) => { bucket.insert(id, data, (error, result) => { if(error) { return reject(error); } resolve({ "id": id }); }); }); } }; |
Observe que nossa função resolver tem apenas um parâmetro, embora estivéssemos passando dois em nosso esquema. Todos os nossos parâmetros de dados residirão na seção dados variável. A própria função espera que retornemos uma promessa.
Você notará que estamos retornando um id e nada mais. Lembre-se, esperamos um Conta a ser devolvido, mas nem todas as partes do Conta precisa estar presente. No entanto, talvez seja uma prática melhor devolver tudo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { createBlog: (data) => { var id = UUID.v4(); data.type = "blog"; return new Promise((resolve, reject) => { bucket.insert(id, data, (error, result) => { if(error) { return reject(error); } resolve({ "id": id }); }); }); } }; |
O criarBlog é praticamente a mesma que a função anterior, com a exceção de que os dados de entrada são diferentes e a função tipo é definida como outra coisa. Todas as nossas funções estarão no mesmo solucionadores objeto.
Com as mutações fora do caminho, os dados podem ser consultados. Pegue o conta por exemplo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var resolvers = { account: (data) => { var id = data.id; return new Promise((resolve, reject) => { bucket.get(id, (error, result) => { if(error) { return reject(error); } resolve(result.value); }); }); } }; |
Nada realmente diferente está acontecendo entre essa função de consulta específica e a função de mutação. Estamos esperando um id a ser passado e estamos usando-o para fazer uma pesquisa.
Para recuperar todas as contas, podemos usar uma consulta N1QL:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { accounts: () => { var statement = "SELECT META(account).id, account.* FROM `" + bucket._name + "` AS account WHERE account.type = 'account'"; var query = Couchbase.N1qlQuery.fromString(statement); return new Promise((resolve, reject) => { bucket.query(query, (error, result) => { if(error) { return reject(error); } resolve(result); }); }); } }; |
Você consegue ver a tendência agora quando se trata de consultar e alterar dados? Para concluir o trabalho, vamos dar uma olhada em nossas duas funções finais, uma para obter um único blog e outra para obter todos os blogs.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var resolvers = { blog: (data) => { var id = data.id; return new Promise((resolve, reject) => { bucket.get(id, (error, result) => { if(error) { return reject(error); } resolve(result.value); }); }); }, blogs: (data) => { var statement = "SELECT META(blog).id, blog.* FROM `" + bucket._name + "` AS blog WHERE blog.type = 'blog' AND blog.account = $account"; var query = Couchbase.N1qlQuery.fromString(statement); return new Promise((resolve, reject) => { bucket.query(query, { "account": data.account }, (error, result) => { if(error) { return reject(error); } resolve(result); }); }); } }; |
Nesse momento, nosso aplicativo está completo. Pelo menos completo o suficiente para este exemplo específico. Agora, provavelmente, devemos testá-lo com algumas consultas GraphQL.
Execução de consultas GraphQL com a interface gráfica
Se estiver usando o Node.js e o pacote GraphQL para o Express, você terá uma boa interface gráfica para executar consultas. Navegue até https://localhost:3000/graphql em seu navegador da Web e você verá algo parecido com o seguinte:

Portanto, vamos criar uma consulta de mutação que inserirá dados para nós.
|
1 2 3 4 5 |
mutation CreateAccount($firstname: String!, $lastname: String!) { createAccount(firstname: $firstname, lastname: $lastname) { id } } |
A mutação acima receberá dois parâmetros obrigatórios e, em seguida, executará nosso createAccount passando cada um desses parâmetros. Os parâmetros podem ser definidos na função Variáveis de consulta do GraphQL explorer. Os parâmetros seriam mais ou menos assim:
|
1 2 3 4 |
{ "firstname": "Erika", "lastname": "Raboy" } |
Após uma mutação bem-sucedida, obteremos o id que é o que solicitamos. Depois de preencher nosso banco de dados com dados, poderíamos fazer uma consulta GraphQL mais sofisticada, como a seguinte:
|
1 2 3 4 5 6 7 8 9 10 |
query GetAccountData($id: String!) { account(id: $id) { firstname lastname } blogs(account: $id) { title content } } |
Para a consulta acima, você passaria uma conta válida e ela retornaria as informações da conta, bem como as informações do blog que essa conta escreveu. Por meio das consultas GraphQL, você pode solicitar determinadas propriedades ou todas as propriedades, evitando que você faça várias solicitações.
Conclusão
Você acabou de ver como criar um banco de dados Node.js simples aproveitando o GraphQL em Couchbase. O fato de o GraphQL oferecer um meio de consultar os dados de que você precisa não elimina a necessidade de escrever consultas de qualidade que se comuniquem com o seu banco de dados. Lembre-se, o GraphQL é apenas uma linguagem de consulta voltada para o cliente, não uma linguagem de consulta de banco de dados de back-end. Em outras palavras, deixe o Couchbase fazer o trabalho pesado quando fizer sentido.
Se você quiser obter mais ajuda com o SDK do Node.js para o Couchbase, consulte o Portal do desenvolvedor do Couchbase.