Introducción
En este tutorial, crearemos una aplicación de pila completa utilizando Vue.js, Node.js, Expressy Servidor Couchbase. Además de estos frameworks, utilizaremos el framework Google Maps y Aquí Lugares API REST.
Qué construiremos
Vamos a crear una aplicación web de una sola página que muestre puntos de interés (POI) alrededor de hoteles seleccionados de una lista de ciudades. Los POI se mostrarán en un mapa interactivo de Google. Aquí tienes una animación que muestra los resultados finales.

Hay algunos giros adicionales para mostrar algunas técnicas más avanzadas.
- Las ciudades se eligen buscando aeropuertos que tengan hoteles cercanos en la misma ciudad.
- Recuperamos los POI mediante una llamada REST, pero los guardamos en nuestra base de datos.
- El lado del cliente recibe datos mediante pushes utilizando eventos enviados por el servidor.
Aunque el código es corto, muestra varias técnicas con la vinculación reactiva de datos de Vue y las características de dependencia de propiedades. Combinado con algunas características potentes de Couchbase, tendremos una aplicación bonita y funcional sin mucho trabajo.
Lo que necesita
La aplicación está construida completamente en JavaScript. Para empezar, solo necesitas unas pocas cosas.
- Node.js instalado
- Servidor Couchbase 5.5.0 o posterior instalado
También tendrás que conseguir llaves para el API JavaScript de Google Maps y el API REST AQUÍ. Ambos pueden utilizarse gratuitamente (con limitaciones).
Los datos para la aplicación vienen como muestra incorporada en la distribución de Couchbase Server.
Primeros pasos
Construiremos la estructura de la aplicación comenzando con el código del cliente web. A continuación viene el lado del servidor Node + código Express. Finalmente veremos el lado del servidor Couchbase.
Examinaremos con más detalle las consultas N1Ql, incluyendo ANSI se une. Esta aplicación utiliza el nuevo Servicio de eventos y funciones. Terminaremos viendo el código JavaScript.
Para empezar, crea un nuevo directorio donde quieras guardar el proyecto. Abra un símbolo del sistema y cambie a ese directorio.
El esqueleto del cliente web
Generación del andamiaje de cliente Vue.js
El cliente web utiliza Vue.js.
Usaremos el CLI de Vue para crear el proyecto base por nosotros. Voy a mostrar una fácil integración entre el cliente y el lado del servidor con webpack. Esto significará reordenar un poco los archivos.
Instala Vue CLI usando npm si aún no lo tienes.
1 |
npm instale -g @vue/cli |
Me gusta usar Bootstrap. Hay al menos un par de proyectos por ahí que integran Boostrap con Vue. Yo elegí Bootstrap-Vue. Esto no es realmente necesario. No es muy difícil eliminar esta dependencia si lo desea.
Crear el boilerplate del proyecto. Aquí es donde entra la plantilla webpack simple. El init
le hará algunas preguntas. Utilizar los valores por defecto está bien.
1 2 |
npm instale -g @vue/cli-init vue init arranque-vue/webpack-simple cliente |
Reestructuración y arreglo
Ahora, cambia al directorio del cliente. Mueve el directorio paquete.json
y .gitignore
creados en un nivel superior. Así se compartirán en todo el proyecto.
1 2 |
cd cliente/ mv paquete.json .gitignore .. |
La configuración de webpack también tiene un pequeño error. Abrir
webpack.config.js
. En la sección que empieza por
1 |
prueba: /\.(png|jpg|gif|svg)$/, |
cambie la línea de opciones para que diga
1 |
nombre: activos/[nombre].[ext]?[hash]' |
Instalar dependencias y compilar
Inicializar e instalar las dependencias de base.
1 |
npm instale |
Instale nuestras otras dependencias. Muchas de ellas son paquetes estándar (morgan, body-parser). Yo uso axios para llamadas de red. canal sse es un buen paquete de eventos enviados por servidor. Es un poco más sofisticado y fácil de usar que otros que he probado. Y hay un paquete para facilitar el trabajo con Google Maps en Vue llamado vue2-google-maps.
Instale el resto de las dependencias de la siguiente manera. Esto incluye lo que necesitaremos para el servidor.
1 |
npm instale --guardar vue2-google-mapas axios express sse-canal dotenv morgan depurar galleta-analizador cuerpo-analizador pájaro azul couchbase |
Esto le dará un front-end basado en Vue. Para construirlo, ya que hemos movido
paquete.json
subir un nivel, necesitamos retocar la sección de scripts npm. Editar paquete.json
en la raíz del proyecto y cambie la línea de compilación a
1 |
"construir": "cd cliente && cross-env NODE_ENV=producción webpack --progress --hide-modules && cp index.html dist/" |
Ahora en el cliente haz
npm run build
.
Puede abrir el index.html
pero no funcionará. Saltaremos adelante para crear el servidor, o puedes intentar arreglar el problema aquí si sólo quieres ver el cliente autónomo.
El esqueleto del servidor web
Vuelva a la raíz del proyecto y prepare el directorio del servidor.
1 2 |
mkdir servidor cd servidor |
Vamos a crear el servidor directamente. Inicie la aplicación base editando un nuevo archivo app.js
. Pega lo siguiente y guárdalo.
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 express = requiere(exprés); const depurar = requiere(depurar)(poi:servidor); const ruta = requiere(camino); const registrador = requiere(morgan); const cookieParser = requiere('cookie-parser'); const bodyParser = requiere('body-parser'); const http = requiere(http); const aplicación = express(); aplicación.utilice(registrador(dev)); aplicación.utilice(bodyParser.json()); aplicación.utilice(bodyParser.urlencoded({ ampliado: falso })); aplicación.utilice(cookieParser()); aplicación.utilice(función(consulte, res, siguiente) { res.cabecera("Access-Control-Allow-Origin" (Control de acceso: permitir origen), "*"); res.cabecera("Access-Control-Allow-Headers" (Control de acceso: permitir encabezados), "Origin, X-Requested-With, Content-Type, Accept"); siguiente(); }); aplicación.utilice(express.estático(ruta.únase a(__dirname, '../cliente'))); // capturar 404 y reenviar al gestor de errores aplicación.utilice(función(consulte, res, siguiente) { consola.dir(consulte); consola.dir(res); deje err = nuevo Error(No encontrado); err.estado = 404; siguiente(err); }); // gestor de errores aplicación.utilice(función(err, consulte, res, siguiente) { // establecer locales, sólo proporciona error en el desarrollo res.locales.mensaje = err.mensaje; res.locales.error = consulte.aplicación.consiga('env') === desarrollo ? err : {}; // mostrar la página de error res.estado(err.estado || 500); res.render(error); }); // Servidor HTTP http.crearServidor(aplicación).escuche(8080); |
Esta es una versión simplificada de la final. Sólo sirve el cliente boilerplate que creamos antes.
En este punto, deberías poder ejecutar node app.js
en el directorio del servidor. Abra una pestaña del navegador y vaya a http://localhost:8080
. Deberías ver algo como esto.

El cliente y el servidor
El código del cliente web
Ahora volveremos y crearemos el cliente real. En el directorio del cliente, bajo el subdirectorio src
Abrir el expediente App.vue
. Actualízalo como sigue.
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 |
<plantilla> <div id="app"> <div clase="contenedor"> <div clase="fila justify-content-center"> <div clase="col-12"> <h2>Puntos de Interés</h2> </div> <div clase="col-md-12"> <b-desplegable id="ciudades" v-vincular:texto=mostrar clase="m-md-2"> <b-desplegable-artículo-botón v-para="ciudad en ciudades" v-vincular:clave="ciudad.nombre" v-en:haga clic en="seleccionado = ciudad">{{ ciudad.nombre }}</b-desplegable-artículo-botón> </b-desplegable> <b-tabla id="destinos" :artículos="destinationsProvider" :campos="campos" @fila-haga clic en="hotelSeleccionado" a rayas pase el ratón por></b-tabla> </div> <div clase="col-md-12"> <GmapMap ref="mapa" estilo="anchura: 100%; altura: 400px;" :zoom="16" :centro="{lat: 43.542619, lng: 6.955665}"> <GmapMarker v-para="(marcador, índice) en poi" :clave="índice" :posición="{ lat: marker.position[0], lng: marker.position[1] }" :icono="{ url: marker.icon }" /> </GmapMap> </div> <div clase="col-8" /> <div clase="col-4" id="eslogan"> Powered por <img src="./activos/logo.png"> </div> </div> </div> </div> </plantilla> importar axios de axios const URL del servidor = ubicación.origen; const servidor = axios.crear({ baseURL: URL del servidor }); const es = nuevo EventSource(`${serverURL}/events/poi`); exportar por defecto { nombre: aplicación, datos() { devolver { campos: [ { clave: nombre, etiqueta: Nombre del hotel, clasificable: verdadero }, { clave: dirección, clasificable: falso }, { clave: nombre del aeropuerto, etiqueta: Nombre del aeropuerto, clasificable: verdadero }, { clave: icao, etiqueta: Código OACI, clasificable: verdadero } ], seleccionado: null, ciudades: [], poi: [] } }, calculado: { mostrar: función() { devolver este.seleccionado ? este.seleccionado.nombre : Elige una ciudad; } }, ver: { seleccionado: función() { este.$raíz.$emite('bv::refresh::table', destinos); } }, métodos: { destinationsProvider(contexto) { si (null === este.seleccionado) devolver []; deje promesa = servidor.consiga(`/records/hotels/byCity/${this.selected.name}``); devolver promesa.entonces(respuesta => { devolver(respuesta.datos); }).captura(error => { devolver []; }); }, hotelSeleccionado(registro, índice) { este.$refs.mapa.panTo({ lat: registro.geo.lat, lng: registro.geo.lon }); servidor.Correo electrónico:('/records/select/geo', registro.geo) .captura(error => { consola.registro(error) }); } }, montado: función() { es.addEventListener(poi, evento => este.poi = JSON.analizar(evento.datos)); servidor.consiga('/registros/destinos') .entonces(respuesta => { este.ciudades = respuesta.datos; }) .captura(error => { consola.registro(error) }); } } #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiasing; -moz-osx-font-smoothing: escala de grises; alinear texto: centro; color: #2c3e50; margin-top: 60px; } #tagline img { altura: 38px; margen: 10px; } h1, h2 { font-weight: normal; } ul { list-style-type: ninguno; acolchado: 0; } li { mostrar: inline-block; margen: 0 10px; } a { color: #42b983; } |
Este es el grueso del código del lado del cliente.
No voy a entrar en detalles sobre la sección de la plantilla o el css. Señalaré un buen elemento. La API de Here devuelve, entre otras cosas, enlaces a iconos adecuados para su uso en Mapas. Si sigues el flujo, verás que los marcadores de mapas cargan esos iconos directamente usando las URLs incluidas.
Conexión de la base de datos Vue
Recorriendo la sección de scripts, verás que hago un uso intensivo de las capacidades reactivas de Vue. Para entender esta parte, te ayudará tener al menos cierta familiaridad con Vue, especialmente con propiedades calculadas y observadores, datos, método y ganchos del ciclo de vida.
Hacemos uso de la montado
para añadir un receptor de eventos enviados por el servidor, y para rellenar inicialmente la lista desplegable de ciudades. La parte más pesada de la lógica de negocio tiene lugar en la consulta a la base de datos, como veremos.
Veamos cómo funciona la selección de una ciudad. Observa que cada elemento del botón desplegable tiene un receptor de clics que establece seleccionado
a los datos de la ciudad para esa entrada. Tenemos un método de vigilancia definido en seleccionado
. Vue también sabe automáticamente que la propiedad calculada mostrar
depende de seleccionado
.
Esto significa que cada vez que se selecciona una ciudad a través del menú desplegable, se produce una cascada de actividad. Cambiar seleccionado
causa mostrar
se vuelva a calcular. Esto, a su vez, establece el texto del botón desplegable, ya que está vinculado a mostrar
. En seleccionado
en el método ver
actualiza la tabla de hoteles cada vez que se selecciona una nueva ciudad.
La mesa artículos
están vinculados a destinationsProvider
en métodos
. Al actualizar la tabla se ejecuta el código. Al igual que la lista de ciudades original, obtiene los hoteles mediante una llamada asíncrona a nuestra base de datos a través de un punto final REST del servidor.
Vue se encarga de mucho aquí por nosotros. Por ejemplo, la llamada para refrescar la tabla no recibe los datos inmediatamente. Vue volverá a renderizar las partes relevantes del DOM automáticamente cada vez que vuelva la llamada REST. No tenemos que suministrar nada del cableado, aparte de especificar el enlace entre artículos
y destinationProvider
.
Completar el Cliente Web
main.js
. Añade una línea de importación y dile a Vue que use el nuevo componente. Aquí está el código final.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
importar config de './config' importar Vue de vue importar BootstrapVue de "bootstrap-vue" importar Aplicación de './App.vue' importar "bootstrap/dist/css/bootstrap.min.css" importar "bootstrap-vue/dist/bootstrap-vue.css" importar * como VueGoogleMaps de vue2-google-maps Vue.utilice(BootstrapVue) Vue.utilice(VueGoogleMaps, { carga: { clave: config.googleMapsKey } }) nuevo Vue({ el: #app, render: h => h(Aplicación) }) |
Cargamos la clave API de Google Maps desde un archivo config.js
. Crea ese archivo y por ahora añade este código de marcador de posición.
1 2 3 |
exportar por defecto { googleMapsKey: '' } |
Vuelva a crear el proyecto (npm run build
). Inicie el servidor, recargue el sitio, y debería ver el comienzo de nuestro cliente real con este aspecto.

El código del servidor web
A continuación completaremos la parte del servidor. Nuestro servidor alimenta las páginas web y expone la API REST que necesitamos. La API es en su mayoría sólo la conveniencia de embalaje en torno a la funcionalidad de base de datos.
En el código fuente del servidor, sustituya nuestro app.js
con esto.
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.Promesa = requiere(pájaro azul); requiere(dotenv).config(); const express = requiere(exprés); const depurar = requiere(depurar)(poi:servidor); const ruta = requiere(camino); const favicon = requiere(servir-favicon); const registrador = requiere(morgan); const cookieParser = requiere('cookie-parser'); const bodyParser = requiere('body-parser'); const http = requiere(http); const https = requiere(https); const fs = requiere(fs); const couchbase = requiere(couchbase); const grupo = nuevo couchbase.Grupo(proceso.env.CLÚSTER); grupo.autentifique(proceso.env.CLUSTER_USER, proceso.env.CONTRASEÑA_CLÚSTER); const aplicación = express(); aplicación.locales.couchbase = couchbase; aplicación.locales.grupo = grupo; aplicación.locales.viaje = grupo.openBucket(muestra-viaje); aplicación.locales.concurso = grupo.openBucket(eventos); aplicación.utilice(favicon(ruta.únase a(__dirname, images/favicon.ico))); aplicación.utilice(registrador(dev)); aplicación.utilice(bodyParser.json()); aplicación.utilice(bodyParser.urlencoded({ ampliado: falso })); aplicación.utilice(cookieParser()); aplicación.utilice(función(consulte, res, siguiente) { res.cabecera("Access-Control-Allow-Origin" (Control de acceso: permitir origen), "*"); res.cabecera("Access-Control-Allow-Headers" (Control de acceso: permitir encabezados), "Origin, X-Requested-With, Content-Type, Accept"); siguiente(); }); aplicación.utilice(express.estático(ruta.únase a(__dirname, '../cliente'))); const registros = requiere('./rutas/registros'); aplicación.utilice(/registros, registros); const eventos = requiere('./rutas/eventos'); aplicación.utilice(/eventos, eventos); // capturar 404 y reenviar al gestor de errores aplicación.utilice(función(consulte, res, siguiente) { consola.dir(consulte); consola.dir(res); deje err = nuevo Error(No encontrado); err.estado = 404; siguiente(err); }); // gestor de errores aplicación.utilice(función(err, consulte, res, siguiente) { // establecer locales, sólo proporciona error en el desarrollo res.locales.mensaje = err.mensaje; res.locales.error = consulte.aplicación.consiga('env') === desarrollo ? err : {}; // mostrar la página de error res.estado(err.estado || 500); res.render(error); }); // Servidor HTTP const puerto_http = proceso.env.HTTP_PORT; const servidor_http = http.crearServidor(aplicación); servidor_http.escuche(puerto_http); servidor_http.en(error, onError); servidor_http.en(escuchar, onListening); // Servidor HTTPS const opciones = { clave: fs.readFileSync(ruta.únase a(ssl, clave.pem)), cert: fs.readFileSync(ruta.únase a(ssl, cert.pem)) }; const puerto_https = proceso.env.HTTPS_PORT; const servidor https = https.crearServidor(opciones, aplicación); servidor https.escuche(puerto_https); servidor https.en(error, onError); servidor https.en(escuchar, onListening); /** * Receptor de eventos de "error" del servidor HTTP/S. */ función onError(error) { si (error.llamada al sistema !== escuchar) { tirar error; } deje vincular = tipode puerto === cadena ? 'Tubo ' + puerto : Puerto + puerto; // gestionar errores de escucha específicos con mensajes amigables interruptor (error.código) { caso EACCES: consola.error(vincular + requiere privilegios elevados); proceso.salida(1); romper; caso EADDRINUSE: consola.error(vincular + ' ya está en uso'); proceso.salida(1); romper; por defecto: tirar error; } } /** * Receptor de eventos de "escucha" del servidor HTTP/S. */ función onListening() { deje dirección = este.dirección(); deje vincular = tipode dirección === cadena ? pipa + dirección : puerto + dirección.puerto; depurar(Escuchando + vincular); } |
Las diferencias clave son la configuración del cliente Couchbase Server Node, y el cableado de las rutas para los puntos finales REST. Hay otro código adicional para cosas como servir sobre http y https, también. No veremos esas partes.
Conexión a Couchbase Server
Los dos bloques de código para conectar con nuestra base de datos son muy sencillos.
1 2 3 4 5 6 7 8 9 10 |
const couchbase = requiere(couchbase); const grupo = nuevo couchbase.Grupo(proceso.env.CLÚSTER); grupo.autentifique(proceso.env.CLUSTER_USER, proceso.env.CONTRASEÑA_CLÚSTER); ... aplicación.locales.couchbase = couchbase; aplicación.locales.grupo = grupo; aplicación.locales.viaje = grupo.openBucket(muestra-viaje); aplicación.locales.concurso = grupo.openBucket(eventos); |
Las tres primeras líneas importan el cliente Couchbase Node, crean un nuevo objeto cluster que representa un cluster de nodos de base de datos, y autentifican a ese cluster. Esto inicia la conexión a la base de datos.
Para mayor comodidad, añadimos referencias a los objetos cliente y cluster a app.locales
. Esto hace que estén disponibles en todo el mundo.
Por último, el código establece y guarda conexiones con dos cubos. Cubos son una estructura organizativa de alto nivel en Couchbase.
El primer bucket lo rellenaremos con datos de ejemplo que vienen con las instalaciones de Couchbase Server. Para el segundo cubo, estoy amañando un poco las cosas aquí. Necesitamos un bucket de metadatos para el archivo Servicio de concursos. Como veremos, sólo necesitamos almacenar un par de documentos adicionales que deben ir a algún sitio aparte del cubo principal. En lugar de crear un tercer bucket, simplemente los pongo con los datos de eventos. Normalmente no usarías este atajo en producción.
Archivos estáticos y rutas API
Tenemos sólo unas pocas líneas de código que necesitamos para dirigir Express para servir nuestras páginas estáticas construidas desde el código del cliente y para organizar nuestra API de datos del servidor.
1 2 3 4 5 6 |
aplicación.utilice(express.estático(ruta.únase a(__dirname, '../cliente'))); const registros = requiere('./rutas/registros'); aplicación.utilice(/registros, registros); const eventos = requiere('./rutas/eventos'); aplicación.utilice(/eventos, eventos); |
La plantilla index.html
La página de inicio de la aplicación añade dist
a todas las rutas de los archivos. Esto significa que nuestros archivos estáticos en realidad se sirven desde un directorio raíz de /cliente/dist
.
He separado la API de datos en dos grupos, organizados bajo un epígrafe general rutas
subdirectorio. Los puntos finales empiezan por registros
. Estos recuperarán datos de la base de datos.
En eventos
es única. Los endpoints son utilizados tanto por el cliente web como por el servicio de eventos de Couchbase.
Veamos la registros
código primero.
API de acceso a bases de datos
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 express = requiere(exprés); const enrutador = express.Router(); enrutador.consiga(/destinos, async función(consulte, res, siguiente) { deje couchbase = consulte.aplicación.locales.couchbase; deje viaje = consulte.aplicación.locales.viaje; deje consultaPromesa = Promesa.prometer(viaje.consulta, { contexto: viaje }); deje consulta = `SELECT DISTINCT aeropuerto.ciudad como nombre DE `viaje-muestra` aeropuerto INNER JOIN `viaje-muestra` hotel USE HASH(sonda) ON hotel.ciudad = aeropuerto.ciudad WHERE aeropuerto.tipo = 'aeropuerto' AND hotel.type = 'hotel';`; consulta = couchbase.N1qlQuery.fromString(consulta); await consultaPromesa(consulta) .entonces(filas => res.json(filas)) .captura(err => { consola.registro(err); res.estado(500).enviar({ error: err }); }); }); enrutador.consiga(/hoteles/porCiudad/:id, async función(consulte, res, siguiente) { deje couchbase = consulte.aplicación.locales.couchbase; deje viaje = consulte.aplicación.locales.viaje; deje consultaPromesa = Promesa.prometer(viaje.consulta, { contexto: viaje }); deje consulta = `SELECT hotel.name, hotel.address, airport.airportname, airport.icao, hotel.geo DE `viaje-muestra` aeropuerto INNER JOIN `viaje-muestra` hotel ON hotel.type = 'hotel' AND hotel.city = airport.city WHERE aeropuerto.tipo = 'aeropuerto' AND airport.city = '${req.params.id}' LIMIT 5;`; consulta = couchbase.N1qlQuery.fromString(consulta); await consultaPromesa(consulta) .entonces(filas => res.json(filas)) .captura(err => { consola.registro(err); res.estado(500).enviar({ error: err }); }); }); enrutador.Correo electrónico:(/select/geo, async función(consulte, res, siguiente) { deje couchbase = consulte.aplicación.locales.couchbase; deje viaje = consulte.aplicación.locales.viaje; deje consultaPromesa = Promesa.prometer(viaje.consulta, { contexto: viaje }); deje ubicación = JSON.stringify(consulte.cuerpo); deje consulta = `UPSERT INTO `viaje-muestra(KEY, VALUE) VALUES('trigger', ${location})`; consulta = couchbase.N1qlQuery.fromString(consulta); await consultaPromesa(consulta) .entonces(respuesta => res.json(respuesta)) .captura(err => { consola.registro(err); res.estado(500).enviar({ error: err }); }); }); módulo.exportaciones = enrutador; |
Aquí tenemos definidas tres rutas, /destinos
, /hoteles/porCiudad/:id
y /selección/geo
. Todos tienen la misma estructura básica. Obtenemos nuestras referencias a la base de datos, utilizamos bluebird para crear una promesa versiones del método de consulta, construir un N1QL dispararla y devolver los resultados.
Repasemos las consultas, empezando por la más sencilla.
Consultas N1QL
Utilizamos La /selección/geo
para almacenar la elección de hotel realizada por el usuario. Aquí está la consulta desglosada.
1 |
UPSERT EN `viaje-muestra` (CLAVE, VALOR) VALORES(gatillo, ${ubicación}) |
UPSERT
modificará un documento, o lo creará si aún no existe. Almacenamos la geolocalización del hotel elegido en un documento con un id de desencadenar
. Probablemente suene extraño. Tendrá más sentido más adelante, cuando lleguemos al código Eventing. Lo que realmente nos interesa no es sólo la ubicación del hotel, sino los puntos de interés cercanos. Este documento pondrá en marcha la secuencia que recupera esos POI. De ahí la razón de llamar al documento desencadenar
.
He aquí un ejemplo del documento creado.
desencadenar
1 2 3 4 5 |
{ "exactitud": "APROXIMADO", "lat": 43.9397954, "lon": 4.805895400000054 } |
Para comprender la /hoteles/porCiudad/:id
consulta, primero eche un vistazo a un par de documentos de ejemplo.
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 |
{ "dirección": "13-15 Avenue Monclar", "alias": null, "checkin": null, "checkout": null, "ciudad": "Avignon", "país": "Francia, "descripción": "Hotel de gestión familiar con vistas a un jardín florido, dentro de un aparcamiento privado. Internet wi-fi disponible en todo el edificio. Habitaciones recientemente renovadas con el típico estilo provenzal. Se hablan 7 idiomas. Servicio de taxi privado"., "direcciones": "justo detrás de la estación central, que da a la avenida principal del centro y a la estación de autobuses", "email": null, "fax": "04 26 23 68 31", "desayuno_gratuito": verdadero, "free_internet": falso, "free_parking": verdadero, "geo": { "exactitud": "APROXIMADO", "lat": 43.9397954, "lon": 4.805895400000054 }, "id": 1359, "nombre": "Avignon Hotel Monclar", "pets_ok": verdadero, "teléfono": "+33 4 90 86 20 14", "precio": "Habitación doble con baño y ducha 30-60 euros, estudios y apartamentos a partir de 75 euros, desayuno 7 euros se puede tomar en el jardín en temporada 7:30AM 11AM", "public_likes": ["Vicente Williamson"], "revisiones": [...], "estado": "Provenza-Alpes-Costa Azul", "título: "Avignon", "tollfree": null, "tipo": "hotel", "url": "http://hotel-monclar.com/en", "vacante": verdadero } |
aeropuerto_1361
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "nombredeaeropuerto": "Caumont", "ciudad": "Avignon", "país": "Francia, "faa": "AVN", "geo": { "alt": 124, "lat": 43.9073, "lon": 4.901831 }, "icao": "LFMV", "id": 1361, "tipo": "aeropuerto", "tz": "Europa/París" } |
Para nuestra tabla de hoteles, necesitamos el nombre del hotel, la dirección, la geolocalización, el nombre del aeropuerto y el código del aeropuerto. Obviamente, eso es combinar datos de ambos documentos. Para ello utilizamos un INNER JOIN
. Esta es la consulta.
1 2 3 4 5 6 7 |
SELECCIONE hotel.nombre, hotel.dirección, aeropuerto.nombre del aeropuerto, aeropuerto.icao, hotel.geo DESDE `viaje-muestra` aeropuerto INTERIOR ÚNASE A `viaje-muestra` hotel EN hotel.tipo = hotel Y hotel.ciudad = aeropuerto.ciudad DONDE aeropuerto.tipo = aeropuerto Y aeropuerto.ciudad = ${req.params.id}' LÍMITE 5; |
Caminando a través de él, se puede ver que somos capaces de realizar la unión utilizando los documentos de la misma cubeta. Utilizo alias para hacer las cosas más claras. Utilizamos la ciudad de cada documento para formar la condición de unión. Observa que también utilizo el documento tipo
tanto en la condición de unión como en la de DONDE
cláusula. Las condiciones de unión pueden ser bastante sofisticadas. Lea esto blog para más detalles y ejemplos.
Por último, examinemos cómo hemos obtenido nuestra lista de ciudades. Esta es la consulta para el /destinos
punto final.
1 2 3 4 5 6 7 |
SELECCIONE DISTINTO aeropuerto.ciudad como nombre DESDE `viaje-muestra` aeropuerto INTERIOR ÚNASE A `viaje-muestra` hotel UTILICE HASH(sonda) EN hotel.ciudad = aeropuerto.ciudad DONDE aeropuerto.tipo = aeropuerto Y hotel.tipo = hotel; |
El único resultado devuelto es una lista de nombres de ciudades. En este caso, estamos utilizando una unión interna como filtro. Al emparejar las ciudades con aeropuertos con las ciudades con hoteles, obtenemos una lista de las ciudades que tienen ambos.
Las uniones internas pueden utilizar dos enfoques algorítmicos diferentes. La primera unión que vimos utiliza la unión de bucle anidado por defecto.
Este último ejemplo utiliza una tabla hash en memoria. Esto puede acelerar considerablemente una unión, sobre todo cuando uno de los dos conjuntos de datos es pequeño. Hemos utilizado la tabla "USE HASH()" para informar a N1QL de cómo queremos optimizar la consulta. Hay un lado "probe" y un lado "build". La tabla hash se construye a partir de los datos del lado de construcción. La unión se realiza haciendo búsquedas en los datos del lado de la sonda.
La pista que dimos arriba le dice a N1QL que use los datos del hotel para el lado de la sonda en este caso. Es decir, se construirá la tabla a partir de los datos del aeropuerto, a continuación, hacer las búsquedas hash utilizando los datos del hotel.
Si no lo has hecho antes, te animo a que pruebes estas consultas directamente en el Couchbase Server Query Workbench, parte de la consola de administración web.
Eventos enviados por el servidor
Ya hemos mencionado la creación de un receptor de eventos para eventos enviados por el servidor en el lado del cliente. Estos dos puntos finales muestran lo que se necesita en el servidor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const express = requiere(exprés); const enrutador = express.Router(); const sse = requiere(sse-canal); const poi = nuevo sse(); enrutador.consiga(/poi, (consulte, res) => poi.addClient(consulte, res)); enrutador.Correo electrónico:(/poi, async función(consulte, res, siguiente) { res.enviar(''); deje msg = { evento: poi }; msg.datos = JSON.stringify(consulte.cuerpo); poi.enviar(msg); }); módulo.exportaciones = enrutador; |
La versión "get" del poi
es llamado por el navegador cuando el EventSource
se construye. Puedes ver que simplemente añadimos la persona que llama como cliente.
Utilizamos la versión "post" como intermediaria para enviar los datos al cliente. El sitio res.send('');
nos da una pista de cómo funciona. En el código Eventing, vamos a utilizar las capacidades N1QL cURL para empujar los datos a este punto final. La respuesta vacía está ahí para cerrar esa transacción.
A continuación, el servidor reenvía los datos a los clientes que estén a la escucha. Hay muchos más detalles. Si quieres saber más, este artículo tiene buena información.
Acabado del servidor
Para terminar el lado servidor de nuestro proyecto, crea un subdirectorio rutas
en el directorio del servidor.
Copie el registros
en un archivo llamado registros.js
. Copia el eventos
en un archivo llamado eventos.js
. Y, por último, en el propio directorio del servidor, cree un nuevo archivo llamado .env
. Pegue allí los siguientes parámetros de configuración y guárdelos. (Por supuesto, cambie los parámetros que necesite).
1 2 3 4 5 6 |
HTTP_PORT=8080 HTTPS_PORT=8081 DEBUG=nodo,http,poi:* CLÚSTER=couchbase://localhost:8091 CLUSTER_USER=Administrador CONTRASEÑA_CLÚSTER=contraseña |
El servidor ya debería estar listo. En el directorio raíz del servidor, ejecute node app.js
. No olvides construir primero el código cliente.
El código del servicio de eventos del servidor Couchbase
Aquí está la última parte de la salsa especial que hace que esta aplicación funcione. En la versión 5.5.0, Couchbase introdujo la función Servicio de concursos. Esta es probablemente mi nueva característica favorita en la serie de lanzamientos 5.5. Couchbase Functions es el primer componente ofrecido como parte de este servicio. En resumen, Functions te permite ejecutar código en el servidor de base de datos en respuesta a los cambios en la base de datos.
Las funciones están escritas en JavaScript estándar, con algunos adiciones y restricciones. Para crear la función que necesitamos, sigue estos pasos.
Cubo de metadatos de eventos
En primer lugar, cree un bucket para los metadatos de eventing.
- Abra la consola de Couchbase Server e inicie sesión si es necesario
- Haga clic en "Cubos" en el menú de la izquierda
- Haga clic en "Añadir cubo" en la esquina superior derecha
- Entre en
concurso
para el nombre del cubo en el cuadro de diálogo que aparece - Haga clic en "Añadir cubo" para terminar
Añadir una función
Ahora, configura la función y añade el código.
- Haga clic en "Eventos" en el menú de la izquierda
- Haga clic en "Añadir función" en la esquina superior derecha
Aparecerá un cuadro de diálogo.
- Seleccione
viaje-muestra
como cubo de origen - Seleccione
concurso
como cubo de metadatos - Entre en
monitor
(o lo que desee) para el Nombre de la función - En "Fijaciones", seleccione
tipo
a "Alias",nombre
a "muestra-viaje", yvalor
a "db" - Haga clic en "Siguiente: Añadir código".
Esto le llevará al editor de código. Está pre-llenado con las firmas de función. Copie este código en su lugar.
https://gist.github.com/HodGreeley/9e25f9072247e180ec5cd764d9048c3b#file-poi-js
Despliegue de una función
Para desplegar este código, primero haga clic en "Guardar" y, a continuación, vuelva a hacer clic en "Eventos" en el menú de la izquierda. Deberías ver una entrada para la función. Haz clic en cualquier lugar de esa barra. Deberías verla expandida.

Haga clic en "Desplegar" y, a continuación, en "Desplegar función".
Comprender el código de función
OnUpdate
se ejecuta cada vez que cambia un documento. Recibe el documento y los metadatos del documento como parámetros.
Buscamos el desencadenar
documento a cambiar, indicando la selección de un nuevo hotel. La primera línea filtra todos los demás documentos basándose en el identificador del documento (a veces denominado clave del documento).
La siguiente línea muestra algunas cosas interesantes. Recupere db
es un alias para el cubo de muestras de viajes. db['aquí']
recupera directamente un documento con id aquí
. Aquí es donde almacenaremos las credenciales necesarias para el AQUÍ servicios de cartografía.
Preparamos la URL y los datos para nuestra solicitud de puntos de interés. Here tiene un montón de funcionalidades interesantes en su API. Sólo vamos a hacer una petición básica.
Con esa información en la mano, estamos listos para nuestra llamada cURL. Construyendo la consulta N1QL, vemos una de las modificaciones al JavaScript estándar: Puedes escribir tus consultas en línea de la misma manera que las construirías en el Query Workbench.
Vemos otro bonito detalle en la consulta cURL. N1QL proporciona una sintaxis conveniente para filtrar los resultados. Añadiendo la ruta .resultados.item
hasta el final, cogemos sólo los datos que queremos.
A continuación ejecutamos la consulta y, utilizando esa misma db[]
taquigrafía, actualice nuestro poi
documento. Éste es un ejemplo de utilización de una función para aumentar los datos. En otro escenario, podríamos derivar nuestra actualización enteramente de los registros de la base de datos. Por ejemplo, podría rellenar todos los detalles de un carro de la compra a medida que un cliente hace selecciones.
Por último, con nuestros puntos de interés en la mano, volvemos a utilizar cURL para empujar los datos a nuestro punto final del servidor web. Recordemos que la versión "post" de poi
API ingiere los datos entrantes y los devuelve a los clientes registrados. Así podemos hacer que la interfaz de usuario del cliente reaccione a los cambios de la base de datos sin tener que sondear.
Pasos finales
Ya estamos listos para montarlo todo. Puedes probar la aplicación tal cual, pero la parte de los mapas no funcionará todavía. Para ello, se necesita una clave de API de Google Maps, y un conjunto de credenciales de HERE.
La tecla Mapas va en el config.js
en el código del cliente. Guarde las claves AQUÍ en un documento en el concurso
cubo. Puede hacerlo directamente en la consola de administración haciendo clic en "Documentos" en el menú de la izquierda y luego en "Añadir documento" en la parte superior derecha. Utilice esto como plantilla.
aquí
1 2 3 4 |
{ "id": "TPxxxxxxxxxxxxxxxxxx", "código": "whsxxxxxxxxxxxxxxxxxxx" } |
Y, por último, como medida de seguridad, cURL está desactivado por defecto. En la consola de administración, haga lo siguiente.
- Haga clic en "Configuración" en el menú de la izquierda
- Haga clic para ampliar "Configuración avanzada de consultas"
- Seleccione "Sin restricciones" en "Acceso a la función CURL()".
Esto es no lo que usted quiere para la producción. En su lugar, usted querría una lista blanca de un conjunto selecto de URLs. Sin embargo, esto servirá para nuestro proyecto.
Con eso, en el directorio del servidor web, ejecute node app.js
. Abrir localhost:8080
en su navegador (o lo que haya elegido en .env
) y pruébalo.
Fuente
Puedes encontrar el código fuente de toda la aplicación en GitHub aquí. He incluido un script configuración
para simplificar la preparación de todo. Sólo tienes que ejecutar ./configuración
e introduzca sus claves. (Puede que tengas que hacerlo ejecutable primero.) Todavía necesitas ejecutar npm instalar
y construir el código del cliente.
Webinar
Esta aplicación se utilizó como parte de una demostración en un webinar de Couchbase. Puedes ver una grabación del mismo aquí. No deje de consultar otros seminarios web en el área de recursos de Couchbase.
Posdata
Couchbase es de código abierto y probar gratis.
Empezar con código de ejemplo, consultas de ejemplo, tutoriales y mucho más.
Más recursos en nuestra portal para desarrolladores.
Síguenos en Twitter @CouchbaseDev.
Puede enviar preguntas a nuestro foros.
Participamos activamente en Stack Overflow.
Envíame tus preguntas, comentarios, temas que te gustaría ver, etc. a Twitter. @HodGreeley
¡Buen post! Gracias.
El primer enlace al repositorio de github está mal. Tiene un ":" al final.