{"id":4175,"date":"2017-12-12T07:00:40","date_gmt":"2017-12-12T15:00:40","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=4175"},"modified":"2025-06-13T18:45:53","modified_gmt":"2025-06-14T01:45:53","slug":"full-stack-node-js-vue-js-couchbase-nosql","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/es\/full-stack-node-js-vue-js-couchbase-nosql\/","title":{"rendered":"Pila completa con Node.js, Vue.js y Couchbase NoSQL"},"content":{"rendered":"<p>Si has estado al d\u00eda, soy un gran defensor del desarrollo Node.js y la pila de desarrollo JavaScript. Anteriormente hab\u00eda escrito sobre el <a href=\"https:\/\/www.thepolyglotdeveloper.com\/2015\/10\/create-a-full-stack-app-using-node-js-couchbase-server\/\" target=\"_blank\" rel=\"noopener\">Pila Couchbase, Express, AngularJS y Node.js (CEAN)<\/a> y <a href=\"https:\/\/www.couchbase.com\/blog\/es\/create-restful-api-node-js-hapi-couchbase-nosql\/\" target=\"_blank\" rel=\"noopener\">modernizaci\u00f3n de la API backend con Hapi.js<\/a>una alternativa popular a Express. Soy un gran fan de Angular, pero recientemente he estado explorando el cada vez m\u00e1s popular framework Vue.js.<\/p>\n<p>Vamos a ver c\u00f3mo crear una aplicaci\u00f3n de pila completa utilizando la pila de JavaScript que consiste en Node.js, Hapi, Vue.js, y <a href=\"https:\/\/www.couchbase.com\/blog\/es\/\" target=\"_blank\" rel=\"noopener\">Couchbase<\/a> NoSQL. Dado que todos los caracteres iniciales de las tecnolog\u00edas son consonantes, no intentar\u00e9 darle un acr\u00f3nimo.<\/p>\n<p><!--more--><\/p>\n<p>La aplicaci\u00f3n que construyamos tendr\u00e1 un modelo de datos semisimplista. Vamos a almacenar informaci\u00f3n sobre personas y direcciones y a configurar c\u00f3mo se relacionan las direcciones con determinadas personas.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-4177 size-full\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/11\/couchbase-vuejs-hapijs-nodejs.png\" alt=\"Hapi.js and Vue.js with Couchbase Project\" width=\"1100\" height=\"515\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs.png 1100w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs-300x140.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs-1024x479.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs-768x360.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs-20x9.png 20w\" sizes=\"auto, (max-width: 1100px) 100vw, 1100px\" \/><\/p>\n<p>El backend Node.js con Hapi va a demostrar el uso de N1QL y las mutaciones de subdocumentos dentro de la base de datos. El <a href=\"https:\/\/vuejs.org\/\" target=\"_blank\" rel=\"noopener\">Vue.js<\/a> frontend nos dar\u00e1 una salida semi-atractiva hacia el trabajo con nuestra API.<\/p>\n<h2>Configuraci\u00f3n de Couchbase para consultas y control de acceso basado en roles<\/h2>\n<p>Antes de que podamos empezar a desarrollar nuestra API RESTful que se comunica con nuestra base de datos NoSQL Couchbase, la base de datos debe estar configurada correctamente. Vamos a suponer que ya has instalado <a href=\"https:\/\/www.couchbase.com\/blog\/es\/downloads\/\" target=\"_blank\" rel=\"noopener\">Servidor Couchbase 5<\/a> o superior.<\/p>\n<p>Con Couchbase listo para funcionar, necesitamos crear un Bucket para guardar nuestros datos. Este tutorial har\u00e1 referencia a un Bucket llamado\u00a0<strong>ejemplo<\/strong>pero en realidad no importa mientras seas coherente.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-4316 size-full\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/12\/couchbase-bucket-example-full-stack.png\" alt=\"Couchbase Example Bucket\" width=\"1100\" height=\"361\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-bucket-example-full-stack.png 1100w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-bucket-example-full-stack-300x98.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-bucket-example-full-stack-1024x336.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-bucket-example-full-stack-768x252.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-bucket-example-full-stack-20x7.png 20w\" sizes=\"auto, (max-width: 1100px) 100vw, 1100px\" \/><\/p>\n<p>El Cubo no necesita ninguna configuraci\u00f3n especial para este ejemplo.<\/p>\n<p>Con el Bucket disponible, necesitamos crear un usuario con permiso para trabajar con el Bucket. Para obtener informaci\u00f3n sobre la creaci\u00f3n de control de acceso basado en roles (RBAC), echa un vistazo a un art\u00edculo anterior que escrib\u00ed titulado,\u00a0<a href=\"https:\/\/www.couchbase.com\/blog\/es\/secure-nosql-data-couchbase-role-based-access-control\/\" target=\"_blank\" rel=\"noopener\">Proteja sus datos NoSQL con el control de acceso basado en roles de Couchbase<\/a>. La cuenta necesitar\u00e1 <strong>Lector de datos<\/strong>, <strong>Redactor de datos<\/strong>y <strong>Seleccionar consulta<\/strong> roles. Esto nos permitir\u00e1 realizar operaciones CRUD contra la base de datos, as\u00ed como ejecutar consultas N1QL.<\/p>\n<p>Por \u00faltimo, debemos preparar un \u00edndice para que admita consultas N1QL.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-4317 size-full\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/12\/couchbase-n1ql-primary-index.png\" alt=\"Couchbase N1QL Primary Index\" width=\"1100\" height=\"343\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-n1ql-primary-index.png 1100w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-n1ql-primary-index-300x94.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-n1ql-primary-index-1024x319.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-n1ql-primary-index-768x239.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/12\/couchbase-n1ql-primary-index-20x6.png 20w\" sizes=\"auto, (max-width: 1100px) 100vw, 1100px\" \/><\/p>\n<p>En un entorno de producci\u00f3n querr\u00e1s crear un \u00edndice para cada consulta que desees ejecutar. Para nuestro ejemplo, vamos a utilizar un \u00edndice primario que es un \u00edndice de prototipado.<\/p>\n<p>Ejecute la siguiente consulta para crear el \u00edndice:<\/p>\n<pre class=\"lang:default decode:true\">CREATE PRIMARY INDEX ON `ejemplo`;<\/pre>\n<p>El \u00edndice primario nos dar\u00e1 la comodidad de poder ejecutar cualquier consulta sobre nuestro Bucket a costa del rendimiento. Los \u00edndices personalizados dar\u00e1n un rendimiento mucho mejor.<\/p>\n<p>En este momento podemos empezar el desarrollo.<\/p>\n<h2>Desarrollo de un backend web con Node.js y Hapi Framework<\/h2>\n<p>Con Couchbase listo, podemos empezar a desarrollar la aplicaci\u00f3n Node.js con Hapi. Si has visto mi <a href=\"https:\/\/www.couchbase.com\/blog\/es\/create-restful-api-node-js-hapi-couchbase-nosql\/\" target=\"_blank\" rel=\"noopener\">art\u00edculo anterior<\/a>mucho del material se mantendr\u00e1. Sin embargo, este ejemplo va a ser un poco m\u00e1s exhaustivo en lo que respecta a lo que Node.js y Couchbase pueden hacer.<\/p>\n<p>Asumiendo que tienes Node.js instalado, necesitamos crear un proyecto fresco. Desde la CLI, ejecuta lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">npm init -y\r\nnpm install hapi joi couchbase uuid --save<\/pre>\n<p>Los comandos anteriores inicializar\u00e1n un nuevo proyecto e instalar\u00e1n la aplicaci\u00f3n <code>hapi<\/code> para Hapi.js, el paquete <code>joi<\/code> para la validaci\u00f3n de datos, el paquete <code>couchbase<\/code> para interactuar con Couchbase, y el paquete <code>uuid<\/code> para generar cadenas \u00fanicas.<\/p>\n<p>A continuaci\u00f3n, cree un\u00a0<strong>app.js<\/strong> que contendr\u00e1 todo nuestro c\u00f3digo Node.js. Para empezar, a\u00f1ade lo siguiente al archivo\u00a0<strong>app.js<\/strong> file:<\/p>\n<pre class=\"lang:default decode:true\">const Couchbase = require(\"couchbase\");\r\nconst Hapi = require(\"hapi\");\r\nconst Joi = require(\"joi\");\r\nconst UUID = require(\"uuid\");\r\n\r\nconst server = new Hapi.Server();\r\nconst cluster = new Couchbase.Cluster(\"couchbase:\/\/localhost\");\r\ncluster.authenticate(\"demo\", \"123456\");\r\nconst bucket = cluster.openBucket(\"example\");\r\n\r\nserver.connection({ host: \"localhost\", port: 3000, routes: { cors: true } });\r\n\r\nbucket.on(\"error\", error =&gt; {\r\n    throw error;\r\n});\r\n\r\nserver.start(error =&gt; {\r\n    if(error) {\r\n        throw error;\r\n    }\r\n    console.log(\"Listening at \" + server.info.uri);\r\n});<\/pre>\n<p>El c\u00f3digo anterior importar\u00e1 nuestras dependencias, se conectar\u00e1 a nuestra instancia de Couchbase utilizando la informaci\u00f3n que hab\u00edamos especificado en el paso anterior y configurar\u00e1 nuestro servidor Hapi para que funcione en https:\/\/localhost:3000.<\/p>\n<p>Cuando definimos nuestra informaci\u00f3n de conexi\u00f3n, elegimos habilitar el uso compartido de recursos entre or\u00edgenes (CORS). Esto permitir\u00e1 que nuestra aplicaci\u00f3n Vue.js se comunique con la aplicaci\u00f3n Node.js aunque est\u00e9n operando desde puertos diferentes. M\u00e1s informaci\u00f3n sobre CORS con Hapi se puede encontrar en un <a href=\"https:\/\/www.thepolyglotdeveloper.com\/2017\/11\/cross-origin-resource-sharing-node-hapi-application\/\" target=\"_blank\" rel=\"noopener\">art\u00edculo anterior<\/a> que escrib\u00ed.<\/p>\n<p>En este punto podemos empezar a definir las rutas de los endpoints a nuestra API.<\/p>\n<p>El objetivo es crear datos para personas y direcciones dentro de Couchbase. Dado que actualmente no existen datos para estas dos categor\u00edas, tiene sentido empezar con endpoints que realicen la creaci\u00f3n de datos.<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"POST\",\r\n    path: \"\/person\",\r\n    config: {\r\n        validate: {\r\n            payload: {\r\n                firstname: Joi.string().required(),\r\n                lastname: Joi.string().required(),\r\n                type: Joi.string().forbidden().default(\"person\"),\r\n                timestamp: Joi.any().forbidden().default((new Date).getTime())\r\n            }\r\n        }\r\n    },\r\n    handler: (request, response) =&gt; {\r\n        var id = UUID.v4();\r\n        bucket.insert(id, request.payload, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            request.payload.id = id;\r\n            response(request.payload);\r\n        });\r\n    }\r\n});<\/pre>\n<p>El c\u00f3digo anterior crear\u00e1 un endpoint que acepta peticiones POST. Suponiendo que la carga \u00fatil JSON enviada con la solicitud cumple los criterios de la l\u00f3gica de validaci\u00f3n, la acci\u00f3n <code>manipulador<\/code> se utilizar\u00e1. Se generar\u00e1 un identificador \u00fanico y la carga \u00fatil se guardar\u00e1 con el identificador creado. En caso de \u00e9xito, la carga \u00fatil con el id se devolver\u00e1 al cliente.<\/p>\n<p>Se puede utilizar una l\u00f3gica similar al crear direcciones:<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"POST\",\r\n    path: \"\/address\",\r\n    config: {\r\n        validate: {\r\n            payload: {\r\n                city: Joi.string().required(),\r\n                state: Joi.string().required(),\r\n                type: Joi.string().forbidden().default(\"address\"),\r\n                timestamp: Joi.any().forbidden().default((new Date).getTime())\r\n            }\r\n        }\r\n    },\r\n    handler: (request, response) =&gt; {\r\n        var id = UUID.v4();\r\n        bucket.insert(id, request.payload, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            request.payload.id = id;\r\n            response(request.payload);\r\n        });\r\n    }\r\n});<\/pre>\n<p>La l\u00f3gica de validaci\u00f3n para las direcciones es un poco diferente, pero todo lo dem\u00e1s sigue siendo igual. Esta misma l\u00f3gica puede aplicarse a pr\u00e1cticamente cualquier punto final de creaci\u00f3n con solicitudes POST.<\/p>\n<p>Con los documentos disponibles en Couchbase, podemos intentar consultarlos. Tiene sentido crear una consulta especializada que encuentre <code>persona<\/code> documentos o <code>direcci\u00f3n<\/code> documentos. Para ello utilizaremos N1QL.<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"GET\",\r\n    path: \"\/addresses\",\r\n    handler: (request, response) =&gt; {\r\n        var statement = \"SELECT META(address).id, address.* FROM example AS address WHERE address.type = 'address'\";\r\n        var query = Couchbase.N1qlQuery.fromString(statement);\r\n        bucket.query(query, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            response(result);\r\n        });\r\n    }\r\n});<\/pre>\n<p>La ruta anterior utilizar\u00e1 una consulta N1QL que obtendr\u00e1 todos los documentos y sus claves de documento siempre que contengan una propiedad llamada <code>tipo<\/code> que es igual a <code>direcci\u00f3n<\/code>.<\/p>\n<p>Del mismo modo, podr\u00edamos hacer lo mismo para <code>persona<\/code> documentos:<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"GET\",\r\n    path: \"\/people\",\r\n    handler: (request, response) =&gt; {\r\n        var statement = \"SELECT META(person).id, person.* FROM example AS person WHERE person.type = 'person'\";\r\n        var query = Couchbase.N1qlQuery.fromString(statement);\r\n        bucket.query(query, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            response(result);\r\n        });\r\n    }\r\n});<\/pre>\n<p>Sin embargo, esto es aburrido s\u00f3lo consulta de documentos basados en su <code>tipo<\/code> propiedad. Probablemente deber\u00edamos establecer una relaci\u00f3n entre ambos tipos.<\/p>\n<p>La idea aqu\u00ed es que creamos un array dentro del archivo <code>persona<\/code> que contiene un identificador para cada direcci\u00f3n a la que est\u00e1n asociados. En lugar de obtener un documento completo, crear o actualizar la matriz, y luego guardar de nuevo, vamos a hacer una mutaci\u00f3n subdocumento directamente en la base de datos.<\/p>\n<p>Tomemos como ejemplo el siguiente punto final:<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"PUT\",\r\n    path: \"\/person\/address\/{personid}\",\r\n    config: {\r\n        validate: {\r\n            payload: {\r\n                addressid: Joi.string().required()\r\n            }\r\n        }\r\n    },\r\n    handler: (request, response) =&gt; {\r\n        bucket.mutateIn(request.params.personid).arrayAppend(\"addresses\", request.payload.addressid, true).execute((error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            bucket.get(request.params.personid, (error, result) =&gt; {\r\n                if(error) {\r\n                    return response({ code: error.code, message: error.message }).code(500);\r\n                }\r\n                response(result.value);\r\n            });\r\n        });\r\n    }\r\n});<\/pre>\n<p>El punto final anterior espera que exista un par\u00e1metro de ruta y una carga \u00fatil en cada solicitud. El par\u00e1metro de ruta es el id de un <code>persona<\/code> y la carga \u00fatil contendr\u00e1 un id de un <code>direcci\u00f3n<\/code> documento.<\/p>\n<p>Utilizando el <code>mutateIn<\/code> podemos proporcionar un documento a mutar, y una ruta a la propiedad que debe ser mutada. En este caso, la propiedad <code>persona<\/code> ser\u00e1 mutado, y a\u00f1adiremos valores a un documento <code>direcciones<\/code> en su interior. Si el <code>direcciones<\/code> no existe, no te preocupes, se crear\u00e1.<\/p>\n<p>Una vez que se produce la mutaci\u00f3n, vamos a bajar todo el documento que hemos mutado y se lo devolveremos al cliente.<\/p>\n<p>Ahora podemos hacer una consulta N1QL m\u00e1s emocionante en nuestros datos. Echa un vistazo a este punto final revisado para recopilar <code>persona<\/code> documentos:<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"GET\",\r\n    path: \"\/people\",\r\n    handler: (request, response) =&gt; {\r\n        var statement = \"SELECT META(person).id, person.firstname, person.lastname, (SELECT address.city, address.state FROM example AS address USE KEYS person.addresses) AS addresses FROM example AS person WHERE person.type = 'person'\";\r\n        var query = Couchbase.N1qlQuery.fromString(statement);\r\n        bucket.query(query, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            response(result);\r\n        });\r\n    }\r\n});<\/pre>\n<p>Ahora estamos haciendo una subconsulta. No estamos haciendo una simple consulta porque una matriz de valores de identificaci\u00f3n de direcciones no es muy \u00fatil para nosotros. En su lugar, la subconsulta carga esos valores de identificaci\u00f3n para que nuestros resultados tengan direcciones reales.<\/p>\n<p>Guay, \u00bfverdad?<\/p>\n<p>Terminemos nuestra API RESTful con un \u00faltimo endpoint.<\/p>\n<pre class=\"lang:default decode:true\">server.route({\r\n    method: \"GET\",\r\n    path: \"\/address\/{addressid}\",\r\n    handler: (request, response) =&gt; {\r\n        bucket.get(request.params.addressid, (error, result) =&gt; {\r\n            if(error) {\r\n                return response({ code: error.code, message: error.message }).code(500);\r\n            }\r\n            response(result.value);\r\n        });\r\n    }\r\n});<\/pre>\n<p>El endpoint anterior nos permitir\u00e1 devolver una \u00fanica direcci\u00f3n concreta en funci\u00f3n de su clave de documento.<\/p>\n<p>Con la API creada, podr\u00edas probarla f\u00e1cilmente con Postman o una herramienta similar. Sin embargo, vamos a crear un frontend para ella con Vue.js.<\/p>\n<h2>Creaci\u00f3n de una interfaz de cliente con Vue.js<\/h2>\n<p>La idea detr\u00e1s del frontend es que haremos peticiones HTTP al backend que acabamos de crear. La mayor parte de nuestro trabajo ser\u00e1 en las solicitudes, la vinculaci\u00f3n de datos, y el atractivo general de nuestra interfaz de usuario.<\/p>\n<p>Si eres nuevo en Vue.js, aseg\u00farate de obtener el Vue CLI. En un directorio nuevo, ejecuta lo siguiente con la CLI de Vue:<\/p>\n<pre class=\"lang:default decode:true\">vue init webpack frontend<\/pre>\n<p>El comando anterior iniciar\u00e1 el proceso de andamiaje para Vue.js. Elija\u00a0<strong>no<\/strong> para todo lo que se pide, ya que no vamos a utilizar todas esas caracter\u00edsticas. Cuando se trata de un proyecto independiente (compilador y tiempo de ejecuci\u00f3n) frente a s\u00f3lo tiempo de ejecuci\u00f3n, no importa para este ejemplo.<\/p>\n<p>Cuando el andamiaje est\u00e9 terminado, ejecuta los siguientes comandos:<\/p>\n<pre class=\"lang:default decode:true\">cd frontend\r\nnpm install<\/pre>\n<p>Los comandos anteriores descargar\u00e1n todas las dependencias necesarias para el proyecto base. Sin embargo, necesitamos una dependencia para hacer peticiones HTTP con Vue.js. Ejecuta lo siguiente desde la CLI:<\/p>\n<pre class=\"lang:default decode:true\">npm install axios --save<\/pre>\n<p>En este proyecto se utilizar\u00e1 el <a href=\"https:\/\/github.com\/axios\/axios\" target=\"_blank\" rel=\"noopener\">axios<\/a> paquete. Para obtener m\u00e1s informaci\u00f3n axios y hacer peticiones HTTP con Vue.js, echa un vistazo a un tutorial anterior que escrib\u00ed titulado,\u00a0<a href=\"https:\/\/www.thepolyglotdeveloper.com\/2017\/10\/consume-api-data-http-vuejs-web-application\/\" target=\"_blank\" rel=\"noopener\">Consumir datos de API remotas a trav\u00e9s de HTTP en una aplicaci\u00f3n web Vue.js<\/a>.<\/p>\n<p>Dentro de su proyecto debe tener un <strong>src\/App.vue<\/strong> archivo. Para simplificar, esta ser\u00e1 una aplicaci\u00f3n de una sola p\u00e1gina y un solo archivo. Ignora cualquier otro componente que haya sido creado con el andamio.<\/p>\n<p>Antes de empezar a a\u00f1adir marcas HTML y l\u00f3gica JavaScript, incluyamos <a href=\"https:\/\/getbootstrap.com\/docs\/3.3\/\" target=\"_blank\" rel=\"noopener\">Bootstrap<\/a> como marco tem\u00e1tico. Abra el proyecto\u00a0<strong>index.html<\/strong> y que tenga el siguiente aspecto:<\/p>\n<pre class=\"lang:default decode:true\">&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n    &lt;head&gt;\r\n        &lt;meta charset=&quot;utf-8&quot;&gt;\r\n        &lt;title&gt;vue-project&lt;\/title&gt;\r\n        &lt;link rel=&quot;stylesheet&quot; href=&quot;https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.7\/css\/bootstrap.min.css&quot; \/&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;div id=&quot;app&quot;&gt;&lt;\/div&gt;\r\n        &lt;!-- built files will be auto injected --&gt;\r\n        &lt;script src=&quot;https:\/\/code.jquery.com\/jquery-3.2.1.min.js&quot;&gt;&lt;\/script&gt;\r\n        &lt;script src=&quot;https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.7\/js\/bootstrap.min.js&quot;&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>La mayor parte de lo anterior se ha extra\u00eddo del <a href=\"https:\/\/getbootstrap.com\/docs\/3.3\/getting-started\/\" target=\"_blank\" rel=\"noopener\">Documentaci\u00f3n de iniciaci\u00f3n a Bootstrap<\/a>. Ahora, cuando empecemos a codificar en el\u00a0<strong>src\/App.vue<\/strong> se ver\u00e1 un poco m\u00e1s atractivo.<\/p>\n<p>Como en la mayor\u00eda de los archivos de proyecto de Vue, hay un archivo <code>&lt;template&gt;<\/code>, <code>&lt;script&gt;<\/code>y <code>&lt;style&gt;<\/code> bloque. Nuestro <code>&lt;style&gt;<\/code> no tendr\u00e1 nada especial, as\u00ed que abre el bloque\u00a0<strong>src\/App.vue<\/strong> e incluya lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">&lt;style&gt;\r\n    #app {\r\n        font-family: 'Avenir', Helvetica, Arial, sans-serif;\r\n        -webkit-font-smoothing: antialiased;\r\n        -moz-osx-font-smoothing: grayscale;\r\n        color: #2c3e50;\r\n        margin-top: 30px;\r\n    }\r\n&lt;\/style&gt;<\/pre>\n<p>La mayor parte de nuestro trabajo estar\u00e1 en los otros dos bloques de c\u00f3digo. Para simplificar, vamos a empezar con la l\u00f3gica y luego terminar con la interfaz de usuario.<\/p>\n<p>Dentro del proyecto\u00a0<strong>src\/App.vue<\/strong> incluya el siguiente c\u00f3digo JavaScript:<\/p>\n<pre class=\"lang:default decode:true\">&lt;script&gt;\r\n    import axios from \"axios\";\r\n    export default {\r\n        name: 'app',\r\n        data() {\r\n            return {\r\n                input: {\r\n                    person: {\r\n                        firstname: \"\",\r\n                        lastname: \"\"\r\n                    },\r\n                    address: {\r\n                        city: \"\",\r\n                        state: \"\"\r\n                    },\r\n                    addressid: \"\"\r\n                },\r\n                people: [],\r\n                addresses: []\r\n            }\r\n        },\r\n        mounted() {\r\n            axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/people\" }).then(result =&gt; {\r\n                this.people = result.data;\r\n            });\r\n            axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/addresses\" }).then(result =&gt; {\r\n                this.addresses = result.data;\r\n            });\r\n        },\r\n        methods: {\r\n            createPerson() {\r\n                if(this.input.person.firstname != \"\" &amp;&amp; this.input.person.lastname != \"\") {\r\n                    axios({ method: \"POST\", url: \"https:\/\/localhost:3000\/person\", data: this.input.person, headers: { \"content-type\": \"application\/json\" }}).then(result =&gt; {\r\n                        this.people.push(result.data);\r\n                        this.input.person.firstname = \"\";\r\n                        this.input.person.lastname = \"\";\r\n                    });\r\n                }\r\n            },\r\n            createAddress() {\r\n                if(this.input.address.city != \"\" &amp;&amp; this.input.address.state != \"\") {\r\n                    axios({ method: \"POST\", url: \"https:\/\/localhost:3000\/address\", data: this.input.address, headers: { \"content-type\": \"application\/json\" }}).then(result =&gt; {\r\n                        this.addresses.push(result.data);\r\n                        this.input.address.city = \"\";\r\n                        this.input.address.state = \"\";\r\n                    });\r\n                }\r\n            },\r\n            linkAddress(personid) {\r\n                if(this.input.addressid != undefined &amp;&amp; personid != \"\") {\r\n                    axios({ method: \"PUT\", url: \"https:\/\/localhost:3000\/person\/address\/\" + personid, data: { addressid: this.input.addressid }, headers: { \"content-type\": \"application\/json\" }}).then(result =&gt; {\r\n                        for(let i = 0; i &lt; this.people.length; i++) {\r\n                            if(this.people[i].id == personid) {\r\n                                if(this.people[i].addresses == undefined) {\r\n                                    this.people[i].addresses = [];\r\n                                }\r\n                                axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/address\/\" + this.input.addressid }).then(result =&gt; {\r\n                                    this.people[i].addresses.push(result.data);\r\n                                    this.input.addressid = \"\";\r\n                                });\r\n                            }\r\n                        }\r\n                    });\r\n                }\r\n            }\r\n        }\r\n    }\r\n&lt;\/script&gt;<\/pre>\n<p>En lo anterior ocurren muchas cosas, as\u00ed que vamos a desglosarlas.<\/p>\n<p>La primera parte de la l\u00f3gica que a\u00f1adimos se refiere a la inicializaci\u00f3n de los datos. La p\u00e1gina <code>datos<\/code> nos permitir\u00e1 inicializar las variables utilizadas en este archivo en particular.<\/p>\n<pre class=\"lang:default decode:true\">data() {\r\n    return {\r\n        input: {\r\n            person: {\r\n                firstname: \"\",\r\n                lastname: \"\"\r\n            },\r\n            address: {\r\n                city: \"\",\r\n                state: \"\"\r\n            },\r\n            addressid: \"\"\r\n        },\r\n        people: [],\r\n        addresses: []\r\n    }\r\n},<\/pre>\n<p>En el <code>datos<\/code> el m\u00e9todo <code>entrada<\/code> se vincular\u00e1 a un formulario en la interfaz de usuario. En nuestro caso tendremos dos formularios, uno para <code>persona<\/code> y uno para <code>direcci\u00f3n<\/code>. Por defecto, queremos que los valores est\u00e9n en blanco. Tambi\u00e9n estamos inicializando nuestra lista de <code>gente<\/code> y nuestra lista de <code>direcciones<\/code> que se obtendr\u00e1 de la API de Node.js.<\/p>\n<p>Despu\u00e9s de inicializar las variables, necesitamos cargar algunos datos del servidor. Esto se puede hacer en el <code>montado<\/code> m\u00e9todo.<\/p>\n<pre class=\"lang:default decode:true\">mounted() {\r\n    axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/people\" }).then(result =&gt; {\r\n        this.people = result.data;\r\n    });\r\n    axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/addresses\" }).then(result =&gt; {\r\n        this.addresses = result.data;\r\n    });\r\n},<\/pre>\n<p>Cuando se carga la aplicaci\u00f3n, se realiza una solicitud de personas y otra de direcciones.<\/p>\n<p>Esto nos lleva a la lista de m\u00e9todos que se pueden llamar desde el HTML. La p\u00e1gina <code>crearPersona<\/code> tomar\u00e1 los datos del formulario de persona y los enviar\u00e1 a la API para guardarlos. Del mismo modo, el m\u00e9todo <code>crearDirecci\u00f3n<\/code> har\u00e1 lo mismo, pero con la informaci\u00f3n de la direcci\u00f3n.<\/p>\n<p>En <code>linkAddress<\/code> es un poco diferente:<\/p>\n<pre class=\"lang:default decode:true\">linkAddress(personid) {\r\n    if(this.input.addressid != undefined &amp;&amp; personid != \"\") {\r\n        axios({ method: \"PUT\", url: \"https:\/\/localhost:3000\/person\/address\/\" + personid, data: { addressid: this.input.addressid }, headers: { \"content-type\": \"application\/json\" }}).then(result =&gt; {\r\n            for(let i = 0; i &lt; this.people.length; i++) {\r\n                if(this.people[i].id == personid) {\r\n                    if(this.people[i].addresses == undefined) {\r\n                        this.people[i].addresses = [];\r\n                    }\r\n                    axios({ method: \"GET\", url: \"https:\/\/localhost:3000\/address\/\" + this.input.addressid }).then(result =&gt; {\r\n                        this.people[i].addresses.push(result.data);\r\n                        this.input.addressid = \"\";\r\n                    });\r\n                }\r\n            }\r\n        });\r\n    }\r\n}<\/pre>\n<p>En <code>linkAddress<\/code> tomar\u00e1 un identificador de persona y un identificador de direcci\u00f3n y los enviar\u00e1 a nuestro punto final de la API de subdocumentos. Una vez hecho esto, actualizar\u00e1 la informaci\u00f3n en las variables locales para mostrarla en la interfaz de usuario.<\/p>\n<p>Esto nos lleva a la parte HTML de nuestro archivo de aplicaci\u00f3n. Dentro del archivo <code>&lt;template&gt;<\/code> deber\u00edamos tener algo como lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">&lt;template&gt;\r\n    &lt;div id=&quot;app&quot;&gt;\r\n        &lt;div class=&quot;container&quot;&gt;\r\n            &lt;div class=&quot;row&quot;&gt;\r\n                &lt;div class=&quot;col-md-6&quot;&gt;\r\n                    &lt;div class=&quot;well&quot;&gt;\r\n                        &lt;form action=&quot;&quot;&gt;\r\n                            &lt;div class=&quot;form-group&quot;&gt;\r\n                                &lt;label for=&quot;firstname&quot;&gt;Nombre&lt;\/label&gt;\r\n                                &lt;input type=&quot;text&quot; v-model=&quot;input.person.firstname&quot; class=&quot;form-control&quot; id=&quot;firstname&quot; placeholder=&quot;Nombre&quot;&gt;\r\n                            &lt;\/div&gt;\r\n                            &lt;div class=&quot;form-group&quot;&gt;\r\n                                &lt;label for=&quot;lastname&quot;&gt;Apellido&lt;\/label&gt;\r\n                                &lt;input type=&quot;text&quot; v-model=&quot;input.person.lastname&quot; class=&quot;form-control&quot; id=&quot;lastname&quot; placeholder=&quot;Apellido&quot;&gt;\r\n                            &lt;\/div&gt;\r\n                            &lt;button type=&quot;button&quot; v-on:click=&quot;createPerson()&quot; class=&quot;btn btn-default&quot;&gt;Guardar&lt;\/button&gt;\r\n                        &lt;input type=&quot;hidden&quot; name=&quot;trp-form-language&quot; value=&quot;es&quot;\/&gt;&lt;\/form&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n                &lt;div class=&quot;col-md-6&quot;&gt;\r\n                    &lt;div class=&quot;well&quot;&gt;\r\n                        &lt;form action=&quot;&quot;&gt;\r\n                            &lt;div class=&quot;form-group&quot;&gt;\r\n                                &lt;label for=&quot;city&quot;&gt;Ciudad&lt;\/label&gt;\r\n                                &lt;input type=&quot;text&quot; v-model=&quot;input.address.city&quot; class=&quot;form-control&quot; id=&quot;city&quot; placeholder=&quot;Ciudad&quot;&gt;\r\n                            &lt;\/div&gt;\r\n                            &lt;div class=&quot;form-group&quot;&gt;\r\n                                &lt;label for=&quot;state&quot;&gt;Estado&lt;\/label&gt;\r\n                                &lt;input type=&quot;text&quot; v-model=&quot;input.address.state&quot; class=&quot;form-control&quot; id=&quot;state&quot; placeholder=&quot;Estado&quot;&gt;\r\n                            &lt;\/div&gt;\r\n                            &lt;button type=&quot;button&quot; v-on:click=&quot;createAddress()&quot; class=&quot;btn btn-default&quot;&gt;Guardar&lt;\/button&gt;\r\n                        &lt;input type=&quot;hidden&quot; name=&quot;trp-form-language&quot; value=&quot;es&quot;\/&gt;&lt;\/form&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/div&gt;\r\n            &lt;div class=&quot;row&quot;&gt;\r\n                &lt;div class=&quot;col-md-12&quot;&gt;\r\n                    &lt;ul class=&quot;list-group&quot;&gt;\r\n                        &lt;li v-for=&quot;(person, index) in people&quot; class=&quot;list-group-item&quot;&gt;\r\n                            {{ person.firstname }} {{ person.lastname }} -\r\n                            &lt;span v-for=&quot;(address, index) in person.addresses&quot;&gt;\r\n                                {{ address.city }}, {{ address.state }} \/\r\n                            &lt;\/span&gt;\r\n                            &lt;p&gt;\r\n                                &lt;form action=&quot;&quot;&gt;\r\n                                    &lt;div v-for=&quot;(address, index) in addresses&quot;&gt;\r\n                                        &lt;input type=&quot;radio&quot; name=&quot;addressid&quot; v-bind:value=&quot;address.id&quot; v-model=&quot;input.addressid&quot;&gt; {{ address.city }}, {{ address.state }}\r\n                                    &lt;\/div&gt;\r\n                                    &lt;button type=&quot;button&quot; v-on:click=&quot;linkAddress(person.id)&quot; class=&quot;btn btn-default&quot;&gt;Guardar&lt;\/button&gt;\r\n                                &lt;input type=&quot;hidden&quot; name=&quot;trp-form-language&quot; value=&quot;es&quot;\/&gt;&lt;\/form&gt;\r\n                            &lt;\/p&gt;\r\n                        &lt;\/li&gt;\r\n                    &lt;\/ul&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/div&gt;\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/template&gt;<\/pre>\n<p>La mayor parte del HTML anterior es Bootstrap boilerplate. Si alguna vez has trabajado con Bootstrap, sabr\u00e1s que hay un mont\u00f3n de preparaci\u00f3n involucrada.<\/p>\n<p>Salta a la siguiente secci\u00f3n:<\/p>\n<pre class=\"lang:default decode:true\">&lt;form action=&quot;&quot;&gt;\r\n    &lt;div class=&quot;form-group&quot;&gt;\r\n        &lt;label for=&quot;firstname&quot;&gt;Nombre&lt;\/label&gt;\r\n        &lt;input type=&quot;text&quot; v-model=&quot;input.person.firstname&quot; class=&quot;form-control&quot; id=&quot;firstname&quot; placeholder=&quot;Nombre&quot;&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=&quot;form-group&quot;&gt;\r\n        &lt;label for=&quot;lastname&quot;&gt;Apellido&lt;\/label&gt;\r\n        &lt;input type=&quot;text&quot; v-model=&quot;input.person.lastname&quot; class=&quot;form-control&quot; id=&quot;lastname&quot; placeholder=&quot;Apellido&quot;&gt;\r\n    &lt;\/div&gt;\r\n    &lt;button type=&quot;button&quot; v-on:click=&quot;createPerson()&quot; class=&quot;btn btn-default&quot;&gt;Guardar&lt;\/button&gt;\r\n&lt;input type=&quot;hidden&quot; name=&quot;trp-form-language&quot; value=&quot;es&quot;\/&gt;&lt;\/form&gt;<\/pre>\n<p>Tenga en cuenta que el <code>entrada<\/code> que hab\u00edamos inicializado est\u00e1 ahora vinculado a los elementos del formulario. Se trata de un enlace de datos bidireccional.<\/p>\n<p>Cuando se pulsa el bot\u00f3n de formulario para este formulario en particular, el bot\u00f3n <code>crearPersona<\/code> es llamado. El otro elemento del formulario se comporta igual.<\/p>\n<p>En la parte principal de la interfaz de usuario, tenemos una lista de datos recuperados del servidor:<\/p>\n<pre class=\"lang:default decode:true\">&lt;ul class=&quot;list-group&quot;&gt;\r\n    &lt;li v-for=&quot;(person, index) in people&quot; class=&quot;list-group-item&quot;&gt;\r\n        {{ person.firstname }} {{ person.lastname }} -\r\n        &lt;span v-for=&quot;(address, index) in person.addresses&quot;&gt;\r\n            {{ address.city }}, {{ address.state }} \/\r\n        &lt;\/span&gt;\r\n        &lt;p&gt;\r\n            &lt;form action=&quot;&quot;&gt;\r\n                &lt;div v-for=&quot;(address, index) in addresses&quot;&gt;\r\n                    &lt;input type=&quot;radio&quot; name=&quot;addressid&quot; v-bind:value=&quot;address.id&quot; v-model=&quot;input.addressid&quot;&gt; {{ address.city }}, {{ address.state }}\r\n                &lt;\/div&gt;\r\n                &lt;button type=&quot;button&quot; v-on:click=&quot;linkAddress(person.id)&quot; class=&quot;btn btn-default&quot;&gt;Guardar&lt;\/button&gt;\r\n            &lt;input type=&quot;hidden&quot; name=&quot;trp-form-language&quot; value=&quot;es&quot;\/&gt;&lt;\/form&gt;\r\n        &lt;\/p&gt;\r\n    &lt;\/li&gt;\r\n&lt;\/ul&gt;<\/pre>\n<p>Estamos listando personas, la informaci\u00f3n de direcci\u00f3n expandida de nuestra consulta N1QL del lado del servidor, y la interacci\u00f3n del bot\u00f3n de radio para emitir la solicitud de subdocumento.<\/p>\n<p>Todo esto se hace f\u00e1cilmente con Vue.js debido a la forma en que la interfaz de usuario est\u00e1 vinculada a la capa l\u00f3gica.<\/p>\n<h2>Conclusi\u00f3n<\/h2>\n<p>Acabas de ver c\u00f3mo crear una aplicaci\u00f3n full stack compuesta estrictamente de tecnolog\u00edas JavaScript. Usamos Node.js con el framework Hapi.js para la capa API backend y el framework Vue.js para nuestra capa frontend de navegador web. El frontend consume datos del backend y el backend obtiene sus datos de nuestro <a href=\"https:\/\/www.couchbase.com\/blog\/es\/\" target=\"_blank\" rel=\"noopener\">Couchbase<\/a> Base de datos NoSQL.<\/p>\n<p>Debido a que hicimos la aplicaci\u00f3n muy modular, cada uno de los componentes puede ser intercambiado con una tecnolog\u00eda diferente. Podr\u00edamos cambiar Node.js para ser Java u otra cosa y podr\u00edamos cambiar Vue.js para ser otra cosa como Angular. Quieres ver c\u00f3mo ir pila completa con Golang y Angular, echa un vistazo a este tutorial que escrib\u00ed titulado,\u00a0<a href=\"https:\/\/www.thepolyglotdeveloper.com\/2017\/02\/build-a-full-stack-movie-database-with-golang-angular-and-nosql\/\" target=\"_blank\" rel=\"noopener\">Crear una base de datos de pel\u00edculas con Golang, Angular y NoSQL<\/a>.<\/p>\n<p>Para obtener m\u00e1s informaci\u00f3n sobre el uso de Couchbase con Node.js, consulte la p\u00e1gina <a href=\"https:\/\/www.couchbase.com\/blog\/es\/developers\/\" target=\"_blank\" rel=\"noopener\">Portal para desarrolladores de Couchbase<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve been keeping up, I&#8217;m a huge advocate for Node.js development and the JavaScript development stack. Previously I had written about the Couchbase, Express, AngularJS, and Node.js (CEAN) stack and modernized the backend API with Hapi.js, a popular alternative [&hellip;]<\/p>","protected":false},"author":63,"featured_media":13873,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1815,1816,1822],"tags":[1393,1725,1745,2083],"ppma_author":[9032],"class_list":["post-4175","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-best-practices-and-tutorials","category-couchbase-server","category-node-js","tag-api","tag-nosql-database","tag-restful","tag-vue-js"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.7.1 (Yoast SEO v25.7) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL<\/title>\n<meta name=\"description\" content=\"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/es\/full-stack-node-js-vue-js-couchbase-nosql\/\" \/>\n<meta property=\"og:locale\" content=\"es_MX\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL\" \/>\n<meta property=\"og:description\" content=\"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/es\/full-stack-node-js-vue-js-couchbase-nosql\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/thepolyglotdeveloper\" \/>\n<meta property=\"article:published_time\" content=\"2017-12-12T15:00:40+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T01:45:53+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1100\" \/>\n\t<meta property=\"og:image:height\" content=\"515\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Nic Raboy, Developer Advocate, Couchbase\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@nraboy\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Nic Raboy, Developer Advocate, Couchbase\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/\"},\"author\":{\"name\":\"Nic Raboy, Developer Advocate, Couchbase\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1\"},\"headline\":\"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL\",\"datePublished\":\"2017-12-12T15:00:40+00:00\",\"dateModified\":\"2025-06-14T01:45:53+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/\"},\"wordCount\":2081,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"keywords\":[\"API\",\"NoSQL Database\",\"restful\",\"vue.js\"],\"articleSection\":[\"Application Design\",\"Best Practices and Tutorials\",\"Couchbase Server\",\"Node.js\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/\",\"name\":\"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"datePublished\":\"2017-12-12T15:00:40+00:00\",\"dateModified\":\"2025-06-14T01:45:53+00:00\",\"description\":\"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"width\":1800,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1\",\"name\":\"Nic Raboy, Developer Advocate, Couchbase\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/8863514d8bed0cf6080f23db40e00354\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/bedeb68368d4681aca4c74fe5f697f0c423b80d498ec50fd915ba018b72c101f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/bedeb68368d4681aca4c74fe5f697f0c423b80d498ec50fd915ba018b72c101f?s=96&d=mm&r=g\",\"caption\":\"Nic Raboy, Developer Advocate, Couchbase\"},\"description\":\"Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.\",\"sameAs\":[\"https:\/\/www.thepolyglotdeveloper.com\",\"https:\/\/www.facebook.com\/thepolyglotdeveloper\",\"https:\/\/x.com\/nraboy\"],\"url\":\"https:\/\/www.couchbase.com\/blog\/es\/author\/nic-raboy-2\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Pila completa con Node.js, Vue.js y Couchbase NoSQL","description":"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/es\/full-stack-node-js-vue-js-couchbase-nosql\/","og_locale":"es_MX","og_type":"article","og_title":"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL","og_description":"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.","og_url":"https:\/\/www.couchbase.com\/blog\/es\/full-stack-node-js-vue-js-couchbase-nosql\/","og_site_name":"The Couchbase Blog","article_author":"https:\/\/www.facebook.com\/thepolyglotdeveloper","article_published_time":"2017-12-12T15:00:40+00:00","article_modified_time":"2025-06-14T01:45:53+00:00","og_image":[{"width":1100,"height":515,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/11\/couchbase-vuejs-hapijs-nodejs.png","type":"image\/png"}],"author":"Nic Raboy, Developer Advocate, Couchbase","twitter_card":"summary_large_image","twitter_creator":"@nraboy","twitter_misc":{"Written by":"Nic Raboy, Developer Advocate, Couchbase","Est. reading time":"10 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/"},"author":{"name":"Nic Raboy, Developer Advocate, Couchbase","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1"},"headline":"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL","datePublished":"2017-12-12T15:00:40+00:00","dateModified":"2025-06-14T01:45:53+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/"},"wordCount":2081,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","keywords":["API","NoSQL Database","restful","vue.js"],"articleSection":["Application Design","Best Practices and Tutorials","Couchbase Server","Node.js"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/","url":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/","name":"Pila completa con Node.js, Vue.js y Couchbase NoSQL","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","datePublished":"2017-12-12T15:00:40+00:00","dateModified":"2025-06-14T01:45:53+00:00","description":"Learn how to create a full stack application with Couchbase as the NoSQL database, Node.js and Hapi for backend, and Vue.js for the frontend.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","width":1800,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/full-stack-node-js-vue-js-couchbase-nosql\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Going Full Stack with Node.js, Vue.js, and Couchbase NoSQL"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"El blog de Couchbase","description":"Couchbase, la base de datos NoSQL","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"El blog de Couchbase","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1","name":"Nic Raboy, Defensor del Desarrollador, Couchbase","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/8863514d8bed0cf6080f23db40e00354","url":"https:\/\/secure.gravatar.com\/avatar\/bedeb68368d4681aca4c74fe5f697f0c423b80d498ec50fd915ba018b72c101f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/bedeb68368d4681aca4c74fe5f697f0c423b80d498ec50fd915ba018b72c101f?s=96&d=mm&r=g","caption":"Nic Raboy, Developer Advocate, Couchbase"},"description":"Nic Raboy es un defensor de las tecnolog\u00edas modernas de desarrollo web y m\u00f3vil. Tiene experiencia en Java, JavaScript, Golang y una variedad de frameworks como Angular, NativeScript y Apache Cordova. Nic escribe sobre sus experiencias de desarrollo relacionadas con hacer el desarrollo web y m\u00f3vil m\u00e1s f\u00e1cil de entender.","sameAs":["https:\/\/www.thepolyglotdeveloper.com","https:\/\/www.facebook.com\/thepolyglotdeveloper","https:\/\/x.com\/nraboy"],"url":"https:\/\/www.couchbase.com\/blog\/es\/author\/nic-raboy-2\/"}]}},"authors":[{"term_id":9032,"user_id":63,"is_guest":0,"slug":"nic-raboy-2","display_name":"Nic Raboy, Developer Advocate, Couchbase","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/bedeb68368d4681aca4c74fe5f697f0c423b80d498ec50fd915ba018b72c101f?s=96&d=mm&r=g","first_name":"Nic","last_name":"Raboy","user_url":"https:\/\/www.thepolyglotdeveloper.com","author_category":"","description":"Nic Raboy es un defensor de las tecnolog\u00edas modernas de desarrollo web y m\u00f3vil. Tiene experiencia en Java, JavaScript, Golang y una variedad de frameworks como Angular, NativeScript y Apache Cordova. Nic escribe sobre sus experiencias de desarrollo relacionadas con hacer el desarrollo web y m\u00f3vil m\u00e1s f\u00e1cil de entender."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts\/4175","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/users\/63"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/comments?post=4175"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts\/4175\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/media\/13873"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/media?parent=4175"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/categories?post=4175"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/tags?post=4175"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/ppma_author?post=4175"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}