Introdução
Neste tutorial, criaremos um aplicativo de pilha completa usando Vue.js, Node.js, Expressoe Servidor Couchbase. Além desses frameworks, usaremos o Google Maps e Aqui Lugares APIs REST.
Para mais informações clonar o repositório aqui.
Conteúdo
O que vamos construir
Vamos criar um aplicativo da Web de página única que mostra pontos de interesse (POI) em torno de hotéis selecionados em uma lista de cidades. Os POIs serão exibidos em um mapa interativo do Google. Aqui está uma animação mostrando os resultados finais.

Há algumas reviravoltas extras para mostrar algumas técnicas mais avançadas.
- As cidades são escolhidas combinando aeroportos que tenham hotéis próximos na mesma cidade.
- Recuperamos os POIs usando uma chamada REST, mas os salvamos em nosso banco de dados.
- O lado do cliente recebe dados por meio de pushes usando eventos enviados pelo servidor.
Embora o código seja curto, ele mostra várias técnicas com os recursos de dependência de propriedade e vinculação de dados reativos do Vue. Combinado com alguns recursos avançados do Couchbase, teremos um aplicativo bom e funcional com pouco trabalho.
O que você precisa
O aplicativo foi desenvolvido inteiramente em JavaScript. Você precisa de apenas alguns elementos para começar.
- Node.js instalado
- Servidor Couchbase 5.5.0 ou posterior instalado
Você também precisará obter chaves para o API JavaScript do Google Maps e o APIs REST da HERE. Ambos podem ser usados gratuitamente (com limitações).
Os dados para o aplicativo são fornecidos como uma amostra incorporada à distribuição do Couchbase Server.
Primeiros passos
Criaremos a estrutura do aplicativo começando com o código do cliente Web. Em seguida, vem o código Node + Express do lado do servidor. Por fim, examinaremos o lado do servidor Couchbase.
Daremos uma olhada mais detalhada nas consultas N1Ql, incluindo Junção com a ANSI. Este aplicativo utiliza o novo Serviço e funções de eventos. Para concluir, examinaremos o código JavaScript.
Para começar, crie um novo diretório no qual você deseja manter o projeto. Abra um prompt de comando e vá para esse diretório.
O esqueleto do cliente Web
Geração do andaime do cliente Vue.js
O cliente web usa Vue.js.
Usaremos o Vue CLI para criar o projeto básico para nós. Mostrarei uma integração fácil entre o lado do cliente e do servidor com o webpack. Para isso, será necessário reorganizar um pouco os arquivos.
Instale o Vue CLI usando o npm, caso ainda não o tenha.
1 |
npm instalar -g @valor/cli |
Gosto de usar Bootstrap. Há pelo menos alguns projetos que integram o Boostrap ao Vue. Eu escolhi o Bootstrap-Vue. Isso não é realmente necessário. Não é muito difícil remover essa dependência, se você quiser.
Criar o boilerplate do projeto. É aqui que entra o modelo simples do Webpack. O modelo inicial
fará algumas perguntas. Não há problema em usar os padrões.
1 2 |
npm instalar -g @valor/cli-inicial valor inicial bootstrap-valor/webpack-simples cliente |
Reestruturação e correção
Agora, vá para o diretório do cliente. Mova o diretório package.json
e .gitignore
criados em um nível superior. Dessa forma, eles serão compartilhados em todo o projeto.
1 2 |
cd cliente/ mv pacote.json .gitignore .. |
A configuração do webpack também tem um pequeno erro. Abrir
webpack.config.js
. Na seção que começa com
1 |
teste: /\.(png|jpg|gif|svg)$/, |
altere a linha de opções para que fique
1 |
nome: 'assets/[name].[ext]?[hash]' |
Instalar dependências e compilar
Inicialize e instale as dependências básicas.
1 |
npm instalar |
Instale nossas outras dependências. Muitas delas são pacotes padrão (morgan, body-parser). Eu uso o áxis para chamadas de rede. canal sse é um bom pacote de eventos enviados pelo servidor. Ele é um pouco mais sofisticado e mais fácil de usar do que outros que já experimentei. E há um pacote para facilitar o trabalho com o Google Maps no Vue chamado vue2-google-maps.
Instale o restante das dependências da seguinte forma. Isso inclui o que precisaremos para o servidor.
1 |
npm instalar --salvar vue2-google-mapas áxis expresso sse-canal dotenv morgan depurar biscoito-analisador corpo-analisador pássaro azul couchbase |
Isso lhe dará um front-end funcional baseado em Vue. Para construí-lo, já que mudamos o
package.json
para um nível superior, precisamos ajustar a seção de scripts npm. Editar package.json
na raiz do projeto e altere a linha de compilação para
1 |
"construir": "cd client && cross-env NODE_ENV=production webpack --progress --hide-modules && cp index.html dist/" |
Agora, no cliente, faça
npm run build
.
Você pode abrir o index.html
agora, mas ele não funcionará. Vamos pular para a criação do servidor, ou você pode tentar corrigir o problema aqui se quiser apenas ver o cliente autônomo.
O esqueleto do servidor web
Navegue de volta para a raiz do projeto e prepare o diretório do servidor.
1 2 |
mkdir servidor cd servidor |
Vamos criar o servidor diretamente. Inicie o aplicativo básico editando um novo arquivo app.js
. Cole o seguinte e salve.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
const expresso = exigir("expresso); const depurar = exigir('debug')('poi:server'); const caminho = exigir('caminho'); const registrador = exigir("morgan); const cookieParser = exigir('cookie-parser'); const bodyParser = exigir('body-parser'); const http = exigir('http'); const aplicativo = expresso(); aplicativo.uso(registrador('dev')); aplicativo.uso(bodyParser.json()); aplicativo.uso(bodyParser.codificado por url({ estendido: falso })); aplicativo.uso(cookieParser()); aplicativo.uso(função(req, res, próxima) { res.cabeçalho("Access-Control-Allow-Origin", "*"); res.cabeçalho("Access-Control-Allow-Headers", "Origem, X-Requested-With, Content-Type, Accept"); próxima(); }); aplicativo.uso(expresso.estático(caminho.unir-se(__dirname, '../cliente'))); // capturar 404 e encaminhar para o manipulador de erros aplicativo.uso(função(req, res, próxima) { console.dir(req); console.dir(res); deixar erro = novo Erro("Não encontrado); erro.status = 404; próxima(erro); }); // manipulador de erros aplicativo.uso(função(erro, req, res, próxima) { // definir locais, fornecendo apenas erros no desenvolvimento res.locais.mensagem = erro.mensagem; res.locais.erro = req.aplicativo.obter('env') === "desenvolvimento ? erro : {}; // renderizar a página de erro res.status(erro.status || 500); res.renderizar('erro'); }); // Servidor HTTP http.createServer(aplicativo).ouvir(8080); |
Esta é uma versão simplificada da final. Ela serve apenas o cliente padrão que criamos anteriormente.
Nesse ponto, você deve ser capaz de executar nó app.js
no diretório do servidor. Abra uma guia do navegador e navegue até http://localhost:8080
. Você deverá ver algo parecido com isto.

Desenvolvendo o cliente e o servidor
O código do cliente Web
Agora, voltaremos e criaremos o cliente real. No diretório do cliente, no subdiretório src
, abra o arquivo App.vue
. Atualize-o da seguinte forma.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
<modelo> <div id="aplicativo"> <div classe="contêiner"> <div classe="row justify-content-center"> <div classe="col-12"> <h2>Pontos de Interesse</h2> </div> <div classe="col-md-12"> <b-menu suspenso id="cidades" v-vincular:texto=exibição classe="m-md-2"> <b-menu suspenso-item-botão v-para="cidade em cidades" v-vincular:chave="city.name" v-em:clique="selected = city" (selecionado = cidade)>{{ cidade.nome }}</b-menu suspenso-item-botão> </b-menu suspenso> <b-tabela id="destinos" :itens="destinationsProvider" :campos="campos" @fila-clicado="hotelSelected" listrado pairar></b-tabela> </div> <div classe="col-md-12"> <GmapMap ref="mapa" estilo="largura: 100%; altura: 400px;" :zoom="16" :centro="{lat: 43.542619, lng: 6.955665}"> <GmapMarker v-para="(marcador, índice) em poi" :chave="índice" :posição="{ lat: marker.position[0], lng: marker.position[1] }" :ícone="{ url: marker.icon }" /> </GmapMap> </div> <div classe="col-8" /> <div classe="col-4" id="tagline"> Alimentado por <imagem src="./assets/logo.png"> </div> </div> </div> </div> </modelo> <script> importação áxis de 'axios' const serverURL = localização.origem; const servidor = áxis.criar({ baseURL: serverURL }); const es = novo Origem do evento(`${serverURL}/events/poi`); exportação padrão { nome: 'aplicativo', dados() { retorno { campos: [ { chave: "nome, rótulo: 'Nome do hotel', classificável: verdadeiro }, { chave: "endereço, classificável: falso }, { chave: "nome do aeroporto, rótulo: 'Nome do aeroporto', classificável: verdadeiro }, { chave: 'icao', rótulo: "Código ICAO, classificável: verdadeiro } ], selecionado: nulo, cidades: [], poi: [] } }, computado: { exibição: função() { retorno este.selecionado ? este.selecionado.nome : 'Escolha uma cidade'; } }, assistir: { selecionado: função() { este.$raiz.$emitir('bv::refresh::table', 'destinos'); } }, métodos: { destinationsProvider(contexto) { se (nulo === este.selecionado) retorno []; deixar promessa = servidor.obter(`/records/hotels/byCity/${this.selected.name}`); retorno promessa.então(resposta => { retorno(resposta.dados); }).captura(erro => { retorno []; }); }, hotelSelected(registro, índice) { este.$refs.mapa.panTo({ lat: registro.geo.lat, lng: registro.geo.solitário }); servidor.postagem('/records/select/geo', registro.geo) .captura(erro => { console.registro(erro) }); } }, montado: função() { es.addEventListener("poi, evento => este.poi = JSON.analisar(evento.dados)); servidor.obter('/records/destinations' (registros/destinos)) .então(resposta => { este.cidades = resposta.dados; }) .captura(erro => { console.registro(erro) }); } } </script> <style> #app { família de fontes: "Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: escala de cinza; alinhamento de texto: centro; cor: #2c3e50; margem superior: 60px; } #tagline img { altura: 38px; margem: 10px; } h1, h2 { peso da fonte: normal; } ul { tipo de estilo de lista: nenhum; acolchoamento: 0; } li { exibição: inline-block; margem: 0 10px; } a { cor: #42b983; } </style> |
Essa é a maior parte do código do lado do cliente.
Não entrarei em detalhes sobre a seção do modelo ou o CSS. Vou destacar um elemento interessante. A API Here retorna, entre outras coisas, links para ícones adequados para uso no Maps. Se você acompanhar o fluxo, verá que os marcadores de mapa estão carregando esses ícones diretamente usando os URLs de inclusão.
Conexão da ligação de dados do Vue
Percorrendo a seção do script, você verá que faço uso intenso dos recursos reativos do Vue. Para entender essa parte, será útil se você tiver pelo menos alguma familiaridade com o Vue, especialmente propriedades computadas e observadores, dados, método e ganchos de ciclo de vida.
Fazemos uso do montado
para adicionar um ouvinte para eventos enviados pelo servidor e para preencher inicialmente a lista suspensa de cidades. O trabalho mais pesado da lógica comercial aqui acontece na consulta ao banco de dados, como veremos.
Vamos acompanhar como funciona a seleção de uma cidade. Observe que cada item no menu suspenso do botão tem um ouvinte de clique vinculado que define selecionado
para os dados da cidade para essa entrada. Temos um método watch definido em selecionado
. O Vue também sabe automaticamente que a propriedade computada exibição
depende de selecionado
.
Isso significa que sempre que uma cidade é selecionada por meio do menu suspenso, temos uma cascata de atividades. Mudança selecionado
causas exibição
a ser recalculado. Isso, por sua vez, define o texto do botão suspenso, já que ele está vinculado a exibição
. O selecionado
no método assistir
aciona uma atualização da tabela de listagem de hotéis sempre que uma nova cidade é selecionada.
A tabela itens
estão vinculados a destinationsProvider
sob métodos
. A atualização da tabela faz com que esse código seja executado. Como a lista de cidades original, ele extrai os hotéis por meio de uma chamada assíncrona ao nosso banco de dados por meio de um endpoint REST do servidor.
O Vue cuida de muitas coisas aqui para nós. Por exemplo, a chamada para atualizar a tabela não recebe dados imediatamente. O Vue renderizará novamente as partes relevantes do DOM automaticamente sempre que a chamada REST retornar. Não precisamos fornecer nenhum dos cabos, a não ser especificar a ligação entre itens
e provedor de destino
.
Conclusão do cliente Web
main.js
. Adicione uma linha de importação e diga ao Vue para usar o novo componente. Aqui está o código final.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
importação configuração de './config' importação Vue de 'vue' importação BootstrapVue de "bootstrap-vue" importação Aplicativo de './App.vue' importação "bootstrap/dist/css/bootstrap.min.css" importação "bootstrap-vue/dist/bootstrap-vue.css" importação * como VueGoogleMaps de 'vue2-google-maps' Vue.uso(BootstrapVue) Vue.uso(VueGoogleMaps, { carregar: { chave: configuração.googleMapsKey } }) novo Vue({ el: '#app', renderizar: h => h(Aplicativo) }) |
Carregamos a chave da API do Google Maps de um arquivo config.js
. Crie esse arquivo e, por enquanto, adicione este código de espaço reservado.
1 2 3 |
exportação padrão { googleMapsKey: '' } |
Crie o projeto novamente (npm run build
). Inicie o servidor, recarregue o site e você verá o início do nosso cliente real com esta aparência.

O código do servidor web
Em seguida, preencheremos o lado do servidor. Nosso servidor alimenta as páginas da Web e expõe a API REST de que precisamos. A API é, em sua maior parte, apenas um pacote de conveniência em torno da funcionalidade do banco de dados.
Na fonte do servidor, substitua nosso app.js
com isso.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
global.Promessa = exigir("pássaro azul); exigir('dotenv').configuração(); const expresso = exigir("expresso); const depurar = exigir('debug')('poi:server'); const caminho = exigir('caminho'); const favicon = exigir('serve-favicon'); const registrador = exigir("morgan); const cookieParser = exigir('cookie-parser'); const bodyParser = exigir('body-parser'); const http = exigir('http'); const https = exigir('https'); const fs = exigir('fs'); const couchbase = exigir('couchbase'); const agrupamento = novo couchbase.Aglomerado(processo.env.CLUSTER); agrupamento.autenticar(processo.env.USUÁRIO DO CLUSTER, processo.env.SENHA DO CLUSTER); const aplicativo = expresso(); aplicativo.locais.couchbase = couchbase; aplicativo.locais.agrupamento = agrupamento; aplicativo.locais.viagens = agrupamento.openBucket('amostra de viagem'); aplicativo.locais.eventos = agrupamento.openBucket("eventos); aplicativo.uso(favicon(caminho.unir-se(__dirname, 'images/favicon.ico'))); aplicativo.uso(registrador('dev')); aplicativo.uso(bodyParser.json()); aplicativo.uso(bodyParser.codificado por url({ estendido: falso })); aplicativo.uso(cookieParser()); aplicativo.uso(função(req, res, próxima) { res.cabeçalho("Access-Control-Allow-Origin", "*"); res.cabeçalho("Access-Control-Allow-Headers", "Origem, X-Requested-With, Content-Type, Accept"); próxima(); }); aplicativo.uso(expresso.estático(caminho.unir-se(__dirname, '../cliente'))); const registros = exigir('./routes/records'); aplicativo.uso('/registros', registros); const eventos = exigir('./routes/events'); aplicativo.uso('/events', eventos); // capturar 404 e encaminhar para o manipulador de erros aplicativo.uso(função(req, res, próxima) { console.dir(req); console.dir(res); deixar erro = novo Erro("Não encontrado); erro.status = 404; próxima(erro); }); // manipulador de erros aplicativo.uso(função(erro, req, res, próxima) { // definir locais, fornecendo apenas erros no desenvolvimento res.locais.mensagem = erro.mensagem; res.locais.erro = req.aplicativo.obter('env') === "desenvolvimento ? erro : {}; // renderizar a página de erro res.status(erro.status || 500); res.renderizar('erro'); }); // Servidor HTTP const http_port = processo.env.HTTP_PORT; const http_server = http.createServer(aplicativo); http_server.ouvir(http_port); http_server.em('erro', onError); http_server.em("ouvindo, onListening); // Servidor HTTPS const opções = { chave: fs.readFileSync(caminho.unir-se('ssl', 'key.pem')), certificado: fs.readFileSync(caminho.unir-se('ssl', 'cert.pem')) }; const https_port = processo.env.HTTPS_PORT; const https_server = https.createServer(opções, aplicativo); https_server.ouvir(https_port); https_server.em('erro', onError); https_server.em("ouvindo, onListening); /** * Ouvinte de eventos para eventos de "erro" do servidor HTTP/S. */ função onError(erro) { se (erro.syscall !== 'ouvir') { lançar erro; } deixar vincular = tipo de porto === 'string' ? Tubo + porto : 'Porto ' + porto; // tratar erros de escuta específicos com mensagens amigáveis interruptor (erro.código) { caso "EACCES: console.erro(vincular + ' requer privilégios elevados'); processo.saída(1); quebra; caso 'EADDRINUSE': console.erro(vincular + ' já está em uso'); processo.saída(1); quebra; padrão: lançar erro; } } /** * Ouvinte de eventos para eventos de "escuta" do servidor HTTP/S. */ função onListening() { deixar endereço = este.endereço(); deixar vincular = tipo de endereço === 'string' ? 'pipe ' + endereço : 'porto ' + endereço.porto; depurar('Listening on ' + vincular); } |
As principais diferenças são a configuração do cliente Node do Couchbase Server e a conexão das rotas para os pontos de extremidade REST. Há outros códigos adicionais para coisas como servir em http e https também. Não examinaremos essas partes.
Conexão com o servidor Couchbase
Os dois blocos de código para conexão ao nosso banco de dados são muito simples.
1 2 3 4 5 6 7 8 9 10 |
const couchbase = exigir('couchbase'); const agrupamento = novo couchbase.Aglomerado(processo.env.CLUSTER); agrupamento.autenticar(processo.env.USUÁRIO DO CLUSTER, processo.env.SENHA DO CLUSTER); ... aplicativo.locais.couchbase = couchbase; aplicativo.locais.agrupamento = agrupamento; aplicativo.locais.viagens = agrupamento.openBucket('amostra de viagem'); aplicativo.locais.eventos = agrupamento.openBucket("eventos); |
As três primeiras linhas importam o cliente Node do Couchbase, criam um novo objeto de cluster que representa um cluster de nós de banco de dados e autenticam esse cluster. Isso inicia a conexão com o banco de dados.
Por conveniência, adicionamos referências aos objetos do cliente e do cluster ao app.locals
. Isso os torna disponíveis globalmente.
Por fim, o código estabelece e salva conexões com dois buckets. Baldes são uma estrutura organizacional de alto nível no Couchbase.
O primeiro bucket será preenchido com dados de amostra que vêm com as instalações do Couchbase Server. Para o segundo bucket, estou fazendo algumas alterações. Precisamos de um bucket de metadados para o Serviço de eventos. Como veremos, precisamos de apenas alguns documentos extras armazenados, que precisam ir para algum lugar além do bucket principal. Em vez de criar um terceiro bucket, eu os coloco junto com os dados de eventos. Normalmente, você não usaria esse atalho na produção.
Arquivos estáticos e rotas de API
Temos apenas algumas linhas de código que precisamos direcionar ao Express para servir nossas páginas estáticas criadas a partir do código do cliente e para organizar nossa API de dados do servidor.
1 2 3 4 5 6 |
aplicativo.uso(expresso.estático(caminho.unir-se(__dirname, '../cliente'))); const registros = exigir('./routes/records'); aplicativo.uso('/registros', registros); const eventos = exigir('./routes/events'); aplicativo.uso('/events', eventos); |
O modelo index.html
A página inicial do aplicativo adiciona dist
para todos os caminhos de arquivos. Isso significa que nossos arquivos estáticos são realmente servidos a partir de um diretório raiz de /cliente/dist
.
Separei a API de dados em dois grupos, organizados em uma categoria geral rotas
subdiretório. Há os pontos de extremidade que começam com registros
. Eles recuperam dados do banco de dados.
O eventos
A rota é exclusiva. Os pontos de extremidade são usados pelo cliente da Web e pelo Couchbase Eventing Service.
Vamos dar uma olhada no registros
código primeiro.
API de acesso ao banco de dados
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
const expresso = exigir("expresso); const roteador = expresso.Roteador(); roteador.obter('/destinos', assíncrono função(req, res, próxima) { deixar couchbase = req.aplicativo.locais.couchbase; deixar viagens = req.aplicativo.locais.viagens; deixar queryPromise = Promessa.prometer(viagens.consulta, { contexto: viagens }); deixar consulta = `SELECT DISTINCT aeroporto.cidade como nome DE `viagens-amostra` aeroporto INNER JOIN `viagens-amostra` hotel USE HASH(probe) ON hotel.city = airport.city WHERE airport.type = 'airport' E hotel.type = 'hotel';`; consulta = couchbase.N1qlQuery.fromString(consulta); aguardar queryPromise(consulta) .então(linhas => res.json(linhas)) .captura(erro => { console.registro(erro); res.status(500).enviar({ erro: erro }); }); }); roteador.obter('/hotels/byCity/:id', assíncrono função(req, res, próxima) { deixar couchbase = req.aplicativo.locais.couchbase; deixar viagens = req.aplicativo.locais.viagens; deixar queryPromise = Promessa.prometer(viagens.consulta, { contexto: viagens }); deixar consulta = `SELECT hotel.name, hotel.address, airport.airportname, airport.icao, hotel.geo DE `viagens-amostra` aeroporto INNER JOIN `viagens-amostra` hotel ON hotel.type = 'hotel' AND hotel.city = airport.city WHERE airport.type = 'airport' E airport.city = '${req.params.id}' LIMIT 5;`; consulta = couchbase.N1qlQuery.fromString(consulta); aguardar queryPromise(consulta) .então(linhas => res.json(linhas)) .captura(erro => { console.registro(erro); res.status(500).enviar({ erro: erro }); }); }); roteador.postagem('/select/geo', assíncrono função(req, res, próxima) { deixar couchbase = req.aplicativo.locais.couchbase; deixar viagens = req.aplicativo.locais.viagens; deixar queryPromise = Promessa.prometer(viagens.consulta, { contexto: viagens }); deixar localização = JSON.stringify(req.corpo); deixar consulta = `UPSERT INTO `viagens-amostra` (KEY, VALUE) VALUES('trigger', ${location})`; consulta = couchbase.N1qlQuery.fromString(consulta); aguardar queryPromise(consulta) .então(resposta => res.json(resposta)) .captura(erro => { console.registro(erro); res.status(500).enviar({ erro: erro }); }); }); módulo.exportações = roteador; |
Temos três rotas definidas aqui, /destinos
, /hotels/byCity/:id
e /select/geo
. Todos eles têm a mesma estrutura básica. Obtemos nossas referências de banco de dados, usamos o bluebird para criar versões de promessa do método de consulta, construímos um N1QL disparar a consulta e retornar os resultados.
Vamos analisar as consultas, começando pela mais simples.
Consultas N1QL
Usamos o /select/geo
para armazenar a escolha atual de hotel feita pelo usuário. Aqui está a consulta dividida.
1 |
UPSERT PARA `viagens-amostra` (CHAVE, VALOR) VALORES('acionador', ${localização}) |
UPSERT
modificará um documento ou o criará se ele ainda não existir. Armazenamos a geolocalização do hotel escolhido em um documento com um id de gatilho
. Isso provavelmente parece estranho. Isso fará mais sentido mais tarde, quando chegarmos ao código Eventing. O que realmente nos interessa não é apenas a localização do hotel, mas os pontos de interesse próximos. Esse documento acionará a sequência que recupera esses POIs. Daí a razão para chamar o documento gatilho
.
Aqui está um exemplo do documento criado.
gatilho
1 2 3 4 5 |
{ "precisão": "APROXIMADO", "lat": 43.9397954, "longo": 4.805895400000054 } |
Para entender o /hotels/byCity/:id
consulta, primeiro dê uma olhada em alguns documentos de exemplo.
hotel_1359
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 26 27 28 29 30 31 32 33 |
{ "endereço": "13-15 Avenue Monclar", "alias": nulo, "checkin": nulo, "checkout": nulo, "cidade": "Avignon", "país": "França", "description" (descrição): "Hotel de gerência familiar com vista para um jardim florido, dentro de um estacionamento privativo. Internet wi-fi disponível em todo o edifício. Quartos recentemente reformados com o típico estilo provençal. Fala-se 7 idiomas. Serviço de táxi particular.", "direções": "logo atrás da estação central, que fica de frente para a avenida principal do centro da cidade e para a estação de ônibus", "email": nulo, "fax": "04 26 23 68 31", "free_breakfast" (café da manhã gratuito): verdadeiro, "free_internet": falso, "free_parking" (estacionamento gratuito): verdadeiro, "geo": { "precisão": "APROXIMADO", "lat": 43.9397954, "longo": 4.805895400000054 }, "id": 1359, "name" (nome): "Avignon Hotel Monclar", "pets_ok": verdadeiro, "telefone": "+33 4 90 86 20 14", "price" (preço): "Quarto duplo com banheiro privativo e chuveiro €30-60, estúdios e apartamentos a partir de €75, café da manhã €7 pode ser tomado no jardim na temporada 7:30AM 11AM", "public_likes": ["Vicente Williamson"], "avaliações": [...], "estado": "Provence-Alpes-Côte d'Azur", "título": "Avignon", "tollfree": nulo, "tipo": "hotel", "url": "http://hotel-monclar.com/en", "vaga": verdadeiro } |
aeroporto_1361
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "nome do aeroporto": "Caumont", "cidade": "Avignon", "país": "França", "faa": "AVN", "geo": { "alt": 124, "lat": 43.9073, "longo": 4.901831 }, "icao": "LFMV", "id": 1361, "tipo": "aeroporto", "tz": "Europa/Paris" } |
Para nossa tabela de hotéis, precisamos do nome do hotel, endereço, geolocalização, nome do aeroporto e código do aeroporto. Obviamente, isso é uma combinação de dados de ambos os documentos. Fazemos isso usando um INNER JOIN
. Aqui está a pergunta.
1 2 3 4 5 6 7 |
SELECIONAR hotel.nome, hotel.endereço, aeroporto.nome do aeroporto, aeroporto.icao, hotel.geo DE `viagens-amostra` aeroporto INNER JUNTAR `viagens-amostra` hotel ON hotel.tipo = 'hotel' E hotel.cidade = aeroporto.cidade ONDE aeroporto.tipo = "aeroporto E aeroporto.cidade = '${req.params.id}' LIMITE 5; |
Ao analisá-lo, você pode ver que conseguimos realizar a união usando documentos do mesmo bucket. Eu uso aliases para deixar as coisas mais claras. Usamos a cidade de cada documento para formar a condição de união. Observe que também uso o documento tipo
tanto na condição de união quanto na condição de ONDE
cláusula. As condições de união podem ser bastante sofisticadas. Leia isto blog para obter mais detalhes e exemplos.
Por fim, vamos examinar como chegamos à nossa lista de cidades em primeiro lugar. Esta é a consulta para o /destinos
ponto final.
1 2 3 4 5 6 7 |
SELECIONAR DISTINTO aeroporto.cidade como nome DE `viagens-amostra` aeroporto INNER JUNTAR `viagens-amostra` hotel USO HASH(sonda) ON hotel.cidade = aeroporto.cidade ONDE aeroporto.tipo = "aeroporto E hotel.tipo = 'hotel'; |
O único resultado retornado é uma lista de nomes de cidades. Nesse caso, estamos usando uma junção interna efetivamente como um filtro. Ao fazer a correspondência entre cidades aeroportuárias e cidades hoteleiras, obtemos uma lista apenas das cidades que têm ambas.
As uniões internas podem usar duas abordagens diferentes em termos de algoritmo. A primeira união que examinamos usa a união de loop aninhado padrão.
Este último exemplo usa uma tabela de hash na memória. Isso pode acelerar significativamente uma união, especialmente quando um dos dois conjuntos de dados é pequeno. Usamos a tabela "USE HASH()" para informar ao N1QL como queremos que a consulta seja otimizada. Há um lado "probe" e um lado "build". A tabela de hash é criada a partir dos dados do lado da construção. A junção é realizada fazendo pesquisas a partir dos dados do lado da sonda.
A dica que demos acima diz ao N1QL para usar os dados do hotel para o lado da sonda nesse caso. Ou seja, ele criará a tabela a partir dos dados do aeroporto e, em seguida, fará as pesquisas de hash usando os dados do hotel.
Se ainda não o fez, recomendo que experimente essas consultas diretamente no Couchbase Server Query Workbench, parte do console de administração da Web.
Eventos enviados pelo servidor
Já mencionamos a configuração de um ouvinte de eventos para eventos enviados pelo servidor no lado do cliente. Esses dois pontos de extremidade mostram o que é necessário no servidor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const expresso = exigir("expresso); const roteador = expresso.Roteador(); const sse = exigir('sse-channel'); const poi = novo sse(); roteador.obter('/poi', (req, res) => poi.addClient(req, res)); roteador.postagem('/poi', assíncrono função(req, res, próxima) { res.enviar(''); deixar mensagem = { evento: "poi }; mensagem.dados = JSON.stringify(req.corpo); poi.enviar(mensagem); }); módulo.exportações = roteador; |
A versão "get" do poi
é chamado pelo navegador quando o ponto de extremidade Origem do evento
é construído. Você pode ver que simplesmente adicionamos o chamador como um cliente.
Usamos a versão "post" como intermediária para enviar dados ao cliente. A versão res.send('');
dá uma dica de como isso funciona. No código Eventing, usaremos os recursos cURL do N1QL para enviar dados para esse endpoint. A resposta vazia está lá para encerrar essa transação.
Em seguida, o servidor encaminha os dados para todos os clientes que estiverem ouvindo. Há muito mais detalhes. Se você quiser saber mais, este artigo tem boas informações.
Finalização do servidor
Para concluir o lado do servidor do nosso projeto, crie um subdiretório rotas
no diretório do servidor.
Copiar o registros
acima em um arquivo em rotas chamado registros.js
. Copiar o eventos
código acima em um arquivo chamado eventos.js
. E, finalmente, no próprio diretório do servidor, crie um novo arquivo chamado .env
. Cole os seguintes parâmetros de configuração e salve. (É claro que você pode alterar as configurações conforme necessário).
1 2 3 4 5 6 |
HTTP_PORT=8080 HTTPS_PORT=8081 DEBUG=nó,http,poi:* CLUSTER='couchbase://localhost:8091' USUÁRIO DO CLUSTER=Administrador SENHA DO CLUSTER=senha |
O servidor deve estar pronto para funcionar agora. No diretório raiz do servidor, execute nó app.js
. Não se esqueça de criar o código do cliente primeiro.
Código do serviço de eventos do Couchbase Server
Aqui está a última parte do molho especial que faz esse aplicativo funcionar. Na versão 5.5.0, o Couchbase introduziu a função Serviço de eventos. Esse é provavelmente o meu novo recurso favorito na série de versões 5.5. O Couchbase Functions é o primeiro componente oferecido como parte desse serviço. Em resumo, o Functions permite que você execute código no servidor de banco de dados em resposta a alterações no banco de dados.
As funções são escritas em JavaScript padrão, com algumas acréscimos e restrições. Para criar a função de que precisamos, siga estas etapas.
Balde de metadados de eventos
Primeiro, crie um compartimento para os metadados de eventos.
- Abra o console do Couchbase Server e faça login, se necessário
- Clique em "Buckets" (Compartimentos) no menu do lado esquerdo
- Clique em "Add Bucket" no canto superior direito
- Entrar
eventos
para o nome do balde na caixa de diálogo que é exibida - Clique em "Add Bucket" para concluir
Adição de uma função
Agora, configure a função e adicione o código.
- Clique em "Eventing" no menu do lado esquerdo
- Clique em "Add Function" (Adicionar função) no canto superior direito
Isso abrirá uma caixa de diálogo.
- Selecione
amostra de viagem
como o Bucket de origem - Selecione
eventos
como o Bucket de Metadados - Entrar
monitor
(ou o que você quiser) para o nome da função - Em "Bindings", defina
tipo
para "Alias",nome
para "travel-sample" evalor
para "db" - Clique em "Next: Adicionar código"
Isso o levará ao editor de código. Ele é pré-preenchido com as assinaturas de função. Em vez disso, copie este código.
https://gist.github.com/HodGreeley/9e25f9072247e180ec5cd764d9048c3b#file-poi-js
Implementação de uma função
Para implementar esse código, primeiro clique em "Save" (Salvar) e, em seguida, clique novamente em "Eventing" (Eventos) no menu do lado esquerdo. Você deverá ver uma entrada para a função. Clique em qualquer lugar dessa barra. Você verá que ela se expande.

Clique em "Deploy" e, em seguida, clique em "Deploy Function".
Entendendo o código de função
Sobre a atualização
é chamado sempre que um documento é alterado. Ele recebe o documento e os metadados do documento como parâmetros.
Estamos procurando o gatilho
documento a ser alterado, indicando a seleção de um novo hotel. A primeira linha filtra todos os outros documentos com base no ID do documento (às vezes chamado de chave do documento).
A próxima linha mostra alguns aspectos interessantes. Lembre-se db
é um alias para o bucket de amostras de viagem. db['here']
recupera diretamente um documento com id aqui
. É aqui que armazenaremos as credenciais necessárias para o AQUI serviços de mapeamento.
Preparamos a URL e os dados para nossa solicitação de pontos de interesse. O Here tem muitos recursos interessantes em sua API. Estamos apenas fazendo uma solicitação básica.
Com essas informações em mãos, estamos prontos para nossa chamada cURL. Ao criar a consulta N1QL, vemos uma das modificações no JavaScript padrão: Você pode escrever suas consultas em linha da mesma forma que as construiria no Query Workbench.
Vemos outro detalhe interessante na consulta cURL. O N1QL oferece uma sintaxe conveniente para filtrar resultados. Ao adicionar o caminho .resultados.item
até o final, obtemos apenas os dados que desejamos.
Em seguida, executamos a consulta e, usando o mesmo db[]
abreviado, atualize nosso poi
documento. Esse é um exemplo de uso de uma função para aumentar os dados. Em outro cenário, podemos derivar nossa atualização inteiramente de registros no banco de dados. Por exemplo, você pode preencher todos os detalhes de um carrinho de compras à medida que um cliente faz seleções.
Por fim, com nossos pontos de interesse em mãos, usamos novamente o cURL para enviar os dados ao endpoint do nosso servidor Web. Lembre-se da versão "post" da função poi
A API ingere os dados recebidos e os envia de volta a todos os clientes registrados. Assim, podemos fazer com que a interface do usuário do cliente reaja às alterações no banco de dados sem precisar fazer pesquisas.
Etapas finais
Agora estamos prontos para juntar tudo isso. Você pode experimentar o aplicativo como está, mas a parte de mapas ainda não funcionará. Para isso, você precisa de uma chave da API do Google Maps e de um conjunto de credenciais da HERE.
A chave do Maps vai para o config.js
no código do cliente. Salve as chaves HERE em um documento na pasta eventos
balde. Você pode fazer isso diretamente no console de administração clicando em "Documents" (Documentos) no menu à esquerda e, em seguida, em "Add Document" (Adicionar documento) no canto superior direito. Use isso como um modelo.
aqui
1 2 3 4 |
{ "id": "TPxxxxxxxxxxxxxxxxxxxxxxxx", "código": "whsxxxxxxxxxxxxxxxxxxxxxxxxx" } |
E, por último, como medida de segurança, o cURL é desativado por padrão. No console de administração, faça o seguinte.
- Clique em "Settings" (Configurações) no menu do lado esquerdo
- Clique para expandir "Configurações avançadas de consulta"
- Selecione "Unrestricted" (Irrestrito) em "CURL() Function Access" (Acesso à função CURL())
Isso é não o que você deseja para a produção. Em vez disso, você desejaria uma lista branca de um conjunto selecionado de URLs. No entanto, isso será suficiente para o nosso projeto.
Com isso, no diretório do servidor da Web, execute nó app.js
. Abrir localhost:8080
em seu navegador (ou o que você escolheu em .env
) e experimentá-lo.
Fonte
Você pode encontrar o código-fonte de todo o aplicativo no GitHub aqui. Incluí um script configuração
para simplificar a preparação de tudo. Basta executar ./setup
e forneça suas chaves. (Talvez seja necessário torná-lo executável primeiro.) Você ainda precisa executar npm install
e criar o código do cliente.
Webinar
Pós-escrito
O Couchbase é de código aberto e grátis para experimentar.
Comece a usar com código de amostra, consultas de exemplo, tutoriais e muito mais.
Encontre mais recursos em nosso portal do desenvolvedor.
Siga-nos no Twitter @CouchbaseDev.
Você pode postar perguntas em nosso fóruns.
Participamos ativamente de Estouro de pilha.
Entre em contato comigo pelo Twitter com perguntas, comentários, tópicos que você gostaria de ver etc. @HodGreeley
Boa postagem! Obrigado.
O primeiro link para o repositório do github é ruim. Ele tem um ":" no final.