Jose NavarroJosé Navarro é um desenvolvedor full stack na FAMOCO em Bruxelas, Bélgica. Ele tem trabalhado nos últimos 3 anos como desenvolvedor web desenvolvedor com Node.js, Java, AngularJS e ReactJS, e tem grande interesse em desenvolvimento web e tecnologias móveis.

Introdução

Vamos desenvolver uma API REST usando o Node.js e o Couchbase ODM Ottoman. Existem algumas estruturas para fazer isso no Node.js, portanto, usaremos o hapi.js, que facilita o início e o desenvolvimento de uma API, e seu código é limpo e fácil de entender. Ele também fornece um validador na solicitação para que possamos nos integrar bem ao modelo Ottoman, que usaremos para abstrair nosso código e trabalhar com objetos.

Requisitos

Para criar o projeto, você precisa ter os seguintes itens instalados em seu computador:

  • Node.js e NPM

  • Servidor Couchbase

Servidor Hapi

Primeiro, criamos o diretório principal do nosso projeto e, em seguida, entramos nesse diretório e iniciamos o projeto npm, no qual serão solicitados alguns parâmetros para o nosso projeto.

Podemos fazer isso com os seguintes comandos:

mkdir nodehapicouchbaseAPI

npm init

A próxima etapa é adicionar as dependências ao nosso projeto. Primeiro, adicionaremos a dependência hapi.js em seguida, adicionamos os pacotes relacionados ao Couchbase e, por fim, adicionamos nodemônio às nossas dependências de desenvolvimento para o recarregamento em tempo real do nosso servidor enquanto estamos programando.

npm install S hapi joi

npm install Otomana com base de sofá S

npm install D nodemon

Quando tudo isso estiver pronto, começaremos a criar nosso projeto. Criamos uma pasta src onde teremos todo o nosso código. Dentro dele, criamos um index.js onde teremos nosso servidor hapi básico. Nesse arquivo, adicionamos o seguinte código:

const Hapi = exigir("hapi);

// Criar um servidor com um host e uma porta

const servidor = novo Hapi.Servidor();

servidor.conexão({

 hospedeiro: 'localhost',

 porto: 5000,

 rotas: {

   cors: verdadeiro,

 }

});

// Iniciar o servidor

servidor.iniciar( erro => {

 se( erro ) {

   // Tratamento de erros sofisticado aqui

   console.erro( erro );

   lançar err;

 }

 console.registro( O servidor foi iniciado em ${ server.info.uri } );

} );

módulo.exportações = servidor;

Acabamos de criar nosso servidor básico.

Agora, vamos definir uma rota de entrada para o nosso servidor. Primeiro, criamos uma pasta API onde definiremos nossas rotas. E criamos um arquivo index.jscom o código de nossa rota de entrada:

const rotas = [

 {

   método: 'GET',

   caminho: ‘/’,

   configuração: {

     manipulador: (solicitação, resposta) => {

       retorno resposta({

         nome: 'node-hapi-couchbase-api',

         versão: 1

       });

     }

   }

 }

];

módulo.exportações = rotas;

No principal index.js vamos importar as rotas. Para isso, adicionamos o seguinte código antes do código server.start que definimos anteriormente:

const rotas = exigir('./api');

// Adicionar as rotas

servidor.rota(rotas);

Agora em nosso package.json adicionaremos o arquivo script seção.

"scripts": {

 "start": "nodemon ./src/index.js"

},

Se executarmos npm startIniciaremos nosso servidor. Podemos verificar isso acessando http://localhost:5000e devemos receber uma resposta.

{"name" (nome):"node-hapi-couchbase-api","versão":1}

Conector de banco de dados

Para configurar o conector de banco de dados, vamos criar uma pasta db onde armazenaremos as informações do banco de dados e a lógica do conector.

Vamos armazenar as informações no arquivo config.json com o seguinte código:

{

 "couchbase": {

   "endpoint": "localhost:8091",

   "bucket" (balde): "api"

 }

}

Para o conector, vamos criar um arquivo index.jsonde importaremos o arquivo de configuração e a biblioteca do Couchbase e inicializaremos a conexão com o banco de dados e o bucket.

deixe a configuração = exigir('./config');

deixe couchbase = exigir('couchbase');

deixe o endpoint = configuração.couchbase.ponto final;

deixe o balde = configuração.couchbase.balde;

let myCluster = novo couchbase.Aglomerado(ponto final, função(erro) {

 se (erro) {

   console.registro("Não é possível se conectar ao couchbase: %s", erro);

 }

 console.registro('conectado ao db %s', ponto final);

});

let myBucket = myCluster.openBucket(balde, função(erro) {

 se (erro) {

   console.registro("Não é possível se conectar ao bucket: %s", erro);

 }

 console.registro('conectado ao bucket %s', balde);

});

A próxima etapa é importar o Couchbase ODM Ottoman e configurá-lo com o bucket.

deixar otomano = exigir("otomano);

otomano.loja = novo otomano.CbStoreAdapter(myBucket, couchbase);

Por fim, vamos exportar o balde e a otomana para termos acesso a partir de outros arquivos.

módulo.exportações = {

 balde: myBucket,

 otomano: otomano

};

Modelos

Agora que temos nosso servidor básico em execução, vamos definir nossos modelos com o Ottoman. Vamos definir dois modelos: um para um Usuário e outro para um Postar. Para isso, criamos uma pasta chamada modelose, dentro dele, criamos dois arquivos js: usuário.js e post.js. Podemos adicionar as validações no modelo, mas o hapi.js oferece uma validação antes de manipular a rota, portanto, vamos usá-la para validar os dados que recebemos do usuário antes de passá-los para o nosso modelo.

Modelo de usuário

O usuário terá três campos: nome, e-mail, e senha. Criamos nosso modelo de usuário usando o pacote Ottoman. Nosso modelo de usuário contém o seguinte código:

deixar otomano = exigir('../db').Otomano;

deixar UserModel = otomano.modelo('Usuário', {

 senha: 'string',

 nome: 'string',

 e-mail: 'string',

}, {

 índice: {

   findByEmail: {

     por: 'email',

     tipo: 'refdoc'

   }

 }

});

Primeiro, importamos a instância do Ottoman que iniciamos no conector db. Depois disso, começamos a definir nosso modelo. O primeiro parâmetro é o nome do nosso modelo, neste caso, "User" (Usuário). O segundo parâmetro é o objeto JSON que contém o nome do campo e o tipo; no nosso caso, todos os valores são string (verifique Ottoman para ver outros tipos). O próximo parâmetro é o objeto que contém o índice que queremos criar. Vamos criar um índice para o e-mail para que possamos usar esse índice para consultar o usuário usando nosso modelo; isso também criará uma restrição para evitar e-mails duplicados em nossos usuários.

Quando criamos um índice, precisamos chamar a função ensureIndices para criar os índices internamente.

otomano.ensureIndices(função(erro) {

 se (erro) {

   retorno console.erro('Error ensure indices USER', erro);

 }

 console.registro('Garantir índices USER');

});

A última etapa é exportar o modelo.

módulo.exportações = UserModel;

Modelo de postagem

A postagem conterá quatro campos: título e corpo, o carimbo de data/horae o usuário.

Primeiro, importamos a instância do Ottoman que inicializamos no conector db e também importamos o modelo User.

deixar otomano = exigir('../db').Otomano;

deixar Usuário = exigir('./usuário');

deixar PostModel = otomano.modelo('Postar', {

 usuário: Usuário,

 título: 'string',

 corpo: 'string',

 carimbo de data/hora: {

   tipo: "Data",

   padrão: Data.agora

 }

});

O primeiro parâmetro é o nome do nosso modelo, "Post". O segundo é o objeto JSON com nosso campo. Nesse caso, definimos o usuário com o tipo Usuário que definimos em nosso modelo anterior; o título e o corpo do tipo stringe registro de data e hora do tipo Data. Vamos criar um valor padrão com o registro de data e hora atual quando o objeto for criado.

E, por fim, exportamos nosso modelo.

módulo.exportações = PostModel;

Rotas de API

Vamos definir nossas rotas para Usuários e Posts; o caminho básico que usaremos é /api/v1. Em nosso arquivo index.js dentro da API, importaremos as rotas de usuário e as rotas de postagem e as juntaremos em uma matriz.

const usuários = exigir('./usuários');

const postagens = exigir('./posts');

rotas = rotas.concatenar(usuários);

rotas = rotas.concatenar(postagens);

Nas rotas User e Post, definiremos os métodos para executar uma operação CRUD. Para cada rota, precisamos definir o método, o caminho e a configuração. Na seção de configuração, fornecemos o manipulador, que é a função a ser executada, e também podemos fornecer uma função de validação que será chamada antes de executarmos a função de manipulação. Para as validações, usaremos o pacote Joi, que pode ser usado para definir o esquema e as validações para o corpo da solicitação.

Rotas de usuário

Para os usuários, usaremos o caminho /api/v1/usuários. A primeira etapa do nosso arquivo de rotas é importar o modelo User e o pacote joi.

const Usuário = exigir('../models/user');

const Joi = exigir("joi);

Recuperar a lista de usuários GET /api/v1/users

Na função handle, usaremos a função find do modelo User, que nos permite consultar o banco de dados para coletar todos os documentos do tipo User.  

 {

   método: 'GET',

   caminho: '/api/v1/users',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Usuário.encontrar({}, (erro, usuários) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         retorno resposta({

           dados: usuários,

           contagem: usuários.comprimento

         });

       });

     }

   }

 }

Vamos retornar um objeto com uma matriz de usuário e uma contagem com o número de objetos dentro da matriz.

Recuperar um usuário por seu ID GET /api/v1/users/{id}

Nesse caso, vamos consultar um usuário pelo ID do documento, portanto, usaremos a função integrada getById em nosso modelo para recuperar um documento do banco de dados.

Nesse caso, fornecemos um validar para validar que o valor do parâmetro id é uma string.

 {

   método: 'GET',

   caminho: '/api/v1/users/{id}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Usuário.getById(solicitação.parâmetros.id, (erro, usuário) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         retorno resposta(usuário);

       });

     },

     validar: {

       parâmetros: {

         id: Joi.string(),

       }

     }

   }

 }

Vamos retornar o documento do usuário.

Criar um novo usuário POST /api/v1/users

Agora vamos criar um novo usuário. A primeira etapa é criar o usuário com o modelo User e o corpo da solicitação.

Fornecemos um objeto de validação para verificar esse payload (corpo da solicitação).

 {

   método: 'POST',

   caminho: '/api/v1/users',

   configuração: {

     manipulador: (solicitação, resposta) => {

       const usuário = novo Usuário(solicitação.carga útil);

       usuário.salvar((erro) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         retorno resposta(usuário).código(201);

       });

     },

     validar: {

       carga útil: {

         senha: Joi.string().alfanum().min(3).máximo(30).necessário(),

         e-mail: Joi.string().e-mail().necessário(),

         nome: Joi.string()

       }

     }

   }

 }

Retornaremos o objeto do novo usuário criado.

Atualizar um usuário PUT /api/v1/users/{id}

Agora vamos atualizar um usuário. Nesse caso, primeiro recuperaremos o documento do usuário no banco de dados, depois atualizaremos os campos e, por fim, salvaremos o documento atualizado no banco de dados.

Nesse caso, fornecemos um objeto validate em que validamos os parâmetros e a carga útil.

 {

   método: 'PUT',

   caminho: '/api/v1/users/{id}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Usuário.getById(solicitação.parâmetros.id, (erro, usuário) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         const carga útil = solicitação.carga útil;

         se (carga útil.nome) {

           usuário.nome = carga útil.nome;

         }

         se (carga útil.senha) {

           usuário.senha = carga útil.senha;

         }

         usuário.salvar((erro) => {

           se (erro) {

             retorno resposta({

               status: 400,

               mensagem: erro.mensagem

             }).código(400);

           }

           retorno resposta(usuário).código(200);

         });

       });

     },

     validar: {

       parâmetros: {

         id: Joi.string(),

       },

       carga útil: {

         nome: Joi.string(),

         senha: Joi.string().alfanum().min(3).máximo(30),

       }

     }

   }

 }

Devolveremos o documento atualizado.

Excluir um usuário DELETE /api/v1/users/{id}

Neste caso, vamos excluir um usuário. Primeiro, recuperamos o documento do banco de dados e, em seguida, o removemos.

 {

   método: 'DELETE',

   caminho: '/api/v1/users/{id}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Usuário.getById(solicitação.parâmetros.id, (erro, usuário) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         usuário.remover((erro) => {

           se (erro) {

             retorno resposta({

               status: 400,

               mensagem: erro.mensagem

             }).código(400);

           }

           retorno resposta(usuário);

         });

       });

     },

     validar: {

       parâmetros: {

         id: Joi.string(),

       }

     }

   }

 }

Devolveremos o documento excluído.

Por fim, precisamos exportar as rotas.

módulo.exportações = rotas;

Rotas de postagem

Para as rotas de postagem, usaremos o caminho /api/v1/users/{userId}/posts, de modo que só realizaremos operações de postagem relacionadas ao usuário. Definiremos uma função de validação que verificará se o usuário existe no banco de dados e a retornará para que tenhamos acesso ao usuário na função que processa a solicitação.

A primeira seção do código são as importações e essa função.

const Usuário = exigir('../models/user');

const Postar = exigir('../models/post');

const Joi = exigir("joi);

const validateUser = (valor, opções, próxima) => {

 const userId = opções.contexto.parâmetros.userId;

 Usuário.getById(userId, (erro, usuário) => {

   próxima(erro, Objeto.atribuir({}, valor, { usuário }))

 })

};

Recuperar a lista de postagens do usuário GET /api/v1/users/{userId}/posts

Para recuperar todas as publicações do usuário, usaremos o modelo Post e a função find. Vamos executar com um objeto em que forneceremos o ID do usuário para recuperar todas as publicações do usuário.

 {

   método: 'GET',

   caminho: '/api/v1/users/{userId}/posts',

   configuração: {

     manipulador: (solicitação, resposta) => {

       const usuário = solicitação.consulta.usuário;

       Postar.encontrar({ usuário: { _id: usuário._id } }, (erro, postagens) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         retorno resposta({

           dados: postagens,

           contagem: postagens.comprimento

         });

       })

     },

     validar: {

       consulta: validateUser,

     }

   }

 }

Vamos retornar um objeto com uma matriz de posts e a contagem de posts.

Recuperar uma postagem GET /api/v1/users/{userId}/posts/{postId}

Como fazemos na lista de publicações, para recuperar uma publicação, chamaremos a função find com o ID do usuário e também com o ID da publicação que queremos recuperar.

 {

   método: 'GET',

   caminho: '/api/v1/users/{userId}/posts/{postId}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       const usuário = solicitação.consulta.usuário;

       const postId = solicitação.parâmetros.postId;

       Postar.encontrar({ usuário: { _id: usuário._id }, _id: postId }, (erro, postagens) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         se (postagens.comprimento === 0) {

           retorno resposta({

             status: 404,

             mensagem: "Não encontrado

           }).código(404);

         } mais {

           retorno resposta(postagens[0]);

         }

       })

     },

     validar: {

       consulta: validateUser,

     }

   }

 }

Vamos retornar a primeira postagem que recebermos. Só podemos receber uma postagem porque estamos consultando o banco de dados para encontrar uma postagem pelo seu ID, e é por isso que retornamos o primeiro item da matriz. Se não recebermos nenhuma postagem, isso significa que não há nenhuma postagem com esse ID relacionada a esse usuário, portanto, retornamos um erro de não encontrado.

Criar uma nova postagem POST /api/v1/users/{userId}/posts

Para criar uma postagem, faremos o mesmo processo que fizemos com o usuário. Fornecemos um objeto validate para a carga útil para que possamos validar o corpo que recebemos. Validamos apenas o título e o corpo da postagem porque o usuário do qual estamos obtendo - o caminho e o carimbo de data/hora - é gerado quando criamos a postagem.

Na função do manipulador, criamos uma nova postagem com a carga útil e definimos o usuário da postagem com o usuário.

 {

   método: 'POST',

   caminho: '/api/v1/users/{userId}/posts',

   configuração: {

     manipulador: (solicitação, resposta) => {

       const usuário = solicitação.consulta.usuário;

       const postagem = novo Postar(solicitação.carga útil);

       postagem.usuário = usuário;

       postagem.salvar((erro) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         retorno resposta(postagem).código(201);

       });

     },

     validar: {

       consulta: validateUser,

       carga útil: {

         título: Joi.string().necessário(),

         corpo: Joi.string().necessário(),

       }

     }

   }

 }

Retornaremos a postagem criada.

Atualizar uma postagem PUT /api/v1/users/{userId}/posts/{postId}

Para atualizar uma postagem, fornecemos um objeto validado, como fizemos na criação, e permitiremos a alteração do título e do corpo da postagem. Aqui, vamos consultar a postagem usando o modelo Post e a função getById, portanto, quando recuperamos a postagem, verificamos se o usuário corresponde ao usuário fornecido no caminho. Se corresponder, atualizamos os campos na postagem com os valores da solicitação e salvamos a postagem atualizada.

   {

   método: 'PUT',

   caminho: '/api/v1/users/{userId}/posts/{postId}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Postar.getById(solicitação.parâmetros.postId, (erro, postagem) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         se (solicitação.parâmetros.userId === postagem.usuário._id) {

           const carga útil = solicitação.carga útil;

           se (carga útil.título) {

             postagem.título = carga útil.título;

           }

           se (carga útil.corpo) {

             postagem.corpo = carga útil.corpo;

           }

           postagem.salvar((erro) => {

             se (erro) {

               retorno resposta({

                 status: 400,

                 mensagem: erro.mensagem

               }).código(400);

             }

             retorno resposta(postagem).código(200);

           });

         } mais {

           retorno resposta({

             status: 401,

             mensagem: "O usuário não pode editar a postagem"

           }).código(401);

         }

       })

     },

     validar: {

       consulta: validateUser,

       carga útil: {

         título: Joi.string().necessário(),

         corpo: Joi.string().necessário(),

       }

     }

   }

 }

Retornaremos a postagem atualizada. Se o usuário não corresponder ao usuário na publicação, receberemos um erro de autorização porque o usuário não é o proprietário da publicação.

Excluir uma postagem DELETE /api/v1/users/{userId}/posts/{postId}

Como fizemos na atualização, consultamos a postagem e verificamos se o usuário do caminho corresponde ao usuário da postagem. Se corresponderem, prosseguimos e excluímos a postagem.

 {

   método: 'DELETE',

   caminho: '/api/v1/users/{userId}/posts/{postId}',

   configuração: {

     manipulador: (solicitação, resposta) => {

       Postar.getById(solicitação.parâmetros.postId, (erro, postagem) => {

         se (erro) {

           retorno resposta({

             status: 400,

             mensagem: erro.mensagem

           }).código(400);

         }

         se (solicitação.parâmetros.userId === postagem.usuário._id) {

           postagem.remover((erro) => {

             se (erro) {

               retorno resposta({

                 status: 400,

                 mensagem: erro.mensagem

               }).código(400);

             }

             retorno resposta(postagem).código(200);

           });

         } mais {

           retorno resposta({

             status: 401,

             mensagem: "O usuário não pode excluir a postagem"

           }).código(401);

         }

       })

     },

     validar: {

       consulta: validateUser

     }

   }

 }

Devolvemos a postagem excluída.

Por fim, exportamos as rotas.

módulo.exportações = rotas;

Teste

Para testar a API, podemos fazer isso com o Postman, cURL ou qualquer outro aplicativo.

Abaixo, criamos alguns exemplos de cURL para testar a API. Os IDs usados são os que criamos com as operações POST, portanto, ao executá-los, lembre-se de alterar o caminho para corresponder aos IDs dos recursos que você gerou.

# consultamos os usuários
enrolar X GET "http://localhost:5000/api/v1/users"

# criamos um usuário
enrolar X POST H "Content-Type: application/json" d ‘{
    "name": "jose",
    "senha": "jose",
    "email": "jose.navarro@famoco.com"
}’ "http://localhost:5000/api/v1/users"

# devemos obter um json com um usuário
enrolar X GET "http://localhost:5000/api/v1/users"

#, devemos obter os usuários com esse ID
enrolar X GET “http://localhost:5000/api/v1/users/e0b66baa-851d-4aae-9ef2-f12575519e5e”

# atualizamos o usuário
enrolar X PUT H "Content-Type: application/json" d ‘{
    "name": "jose_update",
    "senha": "joseedit"
}’ “http://localhost:5000/api/v1/users/e0b66baa-851d-4aae-9ef2-f12575519e5e”

# excluímos o usuário
enrolar X DELETE “http://localhost:5000/api/v1/users/e0b66baa-851d-4aae-9ef2-f12575519e5e”


Mensagens #
# consultamos a postagem de um usuário
enrolar X GET “http://localhost:5000/api/v1/users/e717b7a3-e991-441e-8bca-562f2a572b19/posts”

# criamos uma postagem
enrolar X POST H "Content-Type: application/json" d ‘{
    "title": "my post title",
    "body": "my post body"
}’ “http://localhost:5000/api/v1/users/e717b7a3-e991-441e-8bca-562f2a572b19/posts”

# consultamos para uma postagem
enrolar X GET “http://localhost:5000/api/v1/users/e717b7a3-e991-441e-8bca-562f2a572b19/posts/94b1dd8e-73aa-4e4e-8b29-0870e6515945”

# atualizamos uma postagem
enrolar X PUT H "Content-Type: application/json" d ‘{
    "title": "meu título editado",
    "body": "my edited body" (meu corpo editado)
}’ “http://localhost:5000/api/v1/users/e717b7a3-e991-441e-8bca-562f2a572b19/posts/94b1dd8e-73aa-4e4e-8b29-0870e6515945”

# excluímos uma postagem
enrolar X DELETE “http://localhost:5000/api/v1/users/e717b7a3-e991-441e-8bca-562f2a572b19/posts/94b1dd8e-73aa-4e4e-8b29-0870e6515945”

Conclusão

Como vimos, foi fácil desenvolver uma API REST básica para executar uma operação CRUD, e o código é simples e fácil de ler. E com o Ottoman, conseguimos abstrair a lógica do banco de dados para trabalhar com objetos e com os métodos que o ODM nos forneceu.

Recursos

Hapi.js

Otomana

Repositório do GitHub

Autor

Postado por Laura Czajkowski, gerente da comunidade de desenvolvedores, Couchbase

Laura Czajkowski é a Snr. Developer Community Manager da Couchbase, supervisionando a comunidade. Ela é responsável pelo nosso boletim informativo mensal para desenvolvedores.

Deixar uma resposta