{"id":4892,"date":"2018-03-28T07:00:44","date_gmt":"2018-03-28T14:00:44","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=4892"},"modified":"2025-06-13T18:45:36","modified_gmt":"2025-06-14T01:45:36","slug":"developing-bitcoin-cryptocurrency-application-nodejs-nosql","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/es\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/","title":{"rendered":"Desarrollo de una aplicaci\u00f3n de criptomoneda Bitcoin con Node.js y NoSQL"},"content":{"rendered":"<p>Llevo unos meses siguiendo temas relacionados con las criptomonedas como Bitcoin y estoy muy fascinado con todo lo que est\u00e1 pasando.<\/p>\n<p>Como desarrollador de aplicaciones web, uno de los temas que m\u00e1s me interesan es el de los intercambios de criptomonedas y c\u00f3mo crearlos. De entrada, estas aplicaciones parecen ser herramientas para gestionar cuentas, convertir Bitcoin a una moneda fiduciaria como el USD y viceversa, y transferir Bitcoin a otras personas, pero \u00bfson m\u00e1s?<\/p>\n<p>Vamos a ver algunos ejemplos con Node.js y la base de datos NoSQL, <a href=\"https:\/\/www.couchbase.com\/blog\/es\/\" target=\"_blank\" rel=\"noopener noreferrer\">Couchbase<\/a>que trata temas relacionados con las bolsas de criptomonedas.<\/p>\n<p><b>Actualizaciones sobre temas relacionados:<\/b><\/p>\n<ul>\n<li><a href=\"https:\/\/www.couchbase.com\/blog\/es\/couchbase-blockchain-nosql-database-synergy\/\">La sinergia de Blockchain y las bases de datos NoSQL<\/a><\/li>\n<li><a href=\"https:\/\/www.couchbase.com\/blog\/es\/couchbase-brings-distributed-multi-document-acid-transactions-to-nosql\/\">Transacciones ACID distribuidas multidocumento en Couchbase<\/a><\/li>\n<\/ul>\n<p><!--more--><\/p>\n<p><strong>Descargo de responsabilidad:<\/strong> No soy un experto en criptodivisas, ni he participado en ning\u00fan desarrollo en torno a servicios financieros o intercambios. Soy un entusiasta de la materia y cualquier cosa obtenida de este art\u00edculo debe ser debidamente probado y utilizado bajo su propio riesgo.<\/p>\n<h2>Para llevar<\/h2>\n<p>Hay algunas cosas que obtendr\u00e1s y que no obtendr\u00e1s de este art\u00edculo en particular. Por ejemplo, vamos a empezar con las cosas que no obtendr\u00e1 de este art\u00edculo:<\/p>\n<ul>\n<li>No configuraremos ning\u00fan servicio bancario o de tarjeta de cr\u00e9dito para transferir monedas fiduciarias como el USD.<\/li>\n<li>No emitiremos ninguna transacci\u00f3n firmada a la red Bitcoin, finalizando una transferencia.<\/li>\n<\/ul>\n<p>Dicho esto, he aqu\u00ed algunas cosas que puede esperar aprender en este art\u00edculo:<\/p>\n<ul>\n<li>Crearemos un monedero determinista jer\u00e1rquico (HD) que puede generar una cantidad ilimitada de claves para una semilla dada, cada una representando un monedero de usuario.<\/li>\n<li>Crearemos cuentas de usuario cada una con monederos basados en la semilla maestra.<\/li>\n<li>Crearemos transacciones que representen dep\u00f3sitos, retiradas y transferencias de fondos de la bolsa, sin trabajar realmente con una moneda fiduciaria.<\/li>\n<li>Buscaremos saldos en la red Bitcoin.<\/li>\n<li>Crearemos transacciones firmadas que se difundir\u00e1n en la red Bitcoin.<\/li>\n<\/ul>\n<p>Hay muchas cosas que veremos en este art\u00edculo que se pueden hacer mucho mejor. Si has encontrado algo que se pueda mejorar, comp\u00e1rtelo en los comentarios. Como ya he dicho, no soy un experto en el tema, solo un aficionado.<\/p>\n<h2>Requisitos del proyecto<\/h2>\n<p>Hay algunos requisitos que deben cumplirse para tener \u00e9xito con este proyecto:<\/p>\n<ul>\n<li>Debes tener Node.js 6+ instalado y configurado.<\/li>\n<li>Debes tener Couchbase 5.1+ instalado y configurado con un Bucket y un perfil RBAC listo para funcionar.<\/li>\n<\/ul>\n<p>El punto principal es que no voy a ir a trav\u00e9s de c\u00f3mo obtener Couchbase en marcha y funcionando. No es un proceso dif\u00edcil, pero necesitar\u00e1s un Bucket configurado con una cuenta de aplicaci\u00f3n y un \u00edndice para realizar consultas con N1QL.<\/p>\n<h2>Creaci\u00f3n de una aplicaci\u00f3n Node.js con dependencias<\/h2>\n<p>Vamos a crear una nueva aplicaci\u00f3n Node.js y descargar las dependencias antes de empezar a a\u00f1adir cualquier l\u00f3gica. Crea un directorio de proyecto en alg\u00fan lugar de tu ordenador y ejecuta los siguientes comandos desde un CLI dentro de ese directorio:<\/p>\n<pre class=\"lang:default decode:true\">npm init -y\r\nnpm install couchbase --save\r\nnpm install express --save\r\nnpm install body-parser --save\r\nnpm install joi --save\r\nnpm install request request-promise --save\r\nnpm install uuid --save\r\nnpm install bitcore-lib --save\r\nnpm install bitcore-mnemonic --save<\/pre>\n<p>S\u00e9 que podr\u00eda haber hecho todas las instalaciones de dependencias en una sola l\u00ednea, pero quer\u00eda que fueran claras de leer. Entonces, \u00bfqu\u00e9 estamos haciendo en los comandos anteriores?<\/p>\n<p>En primer lugar, vamos a inicializar un nuevo proyecto Node.js creando un archivo\u00a0<strong>paquete.json<\/strong> archivo. A continuaci\u00f3n, vamos a descargar nuestras dependencias y a\u00f1adirlos a la\u00a0<strong>paquete.json<\/strong> a trav\u00e9s de <code>--guardar<\/code> bandera.<\/p>\n<p>Para este ejemplo utilizaremos Express Framework. El sitio <code>express<\/code>, <code>body-parser<\/code>y <code>joi<\/code> son todos relevantes para aceptar y validar datos de petici\u00f3n. Dado que nos comunicaremos con nodos Bitcoin p\u00fablicos, utilizaremos el paquete <code>solicitar<\/code> y <code>solicitud-promesa<\/code> paquete para envolver promesas. El muy popular <code>bitcore-lib<\/code> nos permitir\u00e1 crear monederos y firmar transacciones mientras que el paquete <code>bitcore-mnem\u00f3nico<\/code> nos permitir\u00e1 generar una semilla que podremos utilizar para nuestras claves de monedero HD. Finalmente, <code>couchbase<\/code> y <code>uuid<\/code> se utilizar\u00e1 para trabajar con nuestra base de datos.<\/p>\n<p>Ahora probablemente queramos estructurar mejor nuestro proyecto. A\u00f1ade los siguientes directorios y archivos dentro del directorio de tu proyecto si no existen ya:<\/p>\n<pre class=\"lang:default decode:true\">package.json\r\nconfig.json\r\napp.js\r\nroutes\r\n    account.js\r\n    transaction.js\r\n    utility.js\r\nclasses\r\n    helper.js<\/pre>\n<p>Todos nuestros puntos finales de API se dividir\u00e1n en categor\u00edas y se colocar\u00e1n en cada archivo de enrutamiento apropiado. No tenemos que hacer esto, pero hace que nuestro proyecto sea un poco m\u00e1s limpio. Para eliminar una tonelada de Bitcoin y la l\u00f3gica de base de datos fuera de nuestras rutas, vamos a a\u00f1adir todo lo que no es la validaci\u00f3n de datos en nuestra\u00a0<strong>clases\/helper.js<\/strong> expediente. La direcci\u00f3n\u00a0<strong>config.json<\/strong> tendr\u00e1 toda la informaci\u00f3n de nuestra base de datos, as\u00ed como nuestra semilla mnemot\u00e9cnica. En un escenario realista, este archivo deber\u00eda ser tratado como oro y recibir toda la protecci\u00f3n posible. La direcci\u00f3n\u00a0<strong>app.js<\/strong> tendr\u00e1 toda nuestra configuraci\u00f3n y l\u00f3gica de arranque para conectar nuestras rutas, conectarse a la base de datos, etc.<\/p>\n<p>Por comodidad, vamos a a\u00f1adir una dependencia m\u00e1s a nuestro proyecto y configurarlo:<\/p>\n<pre class=\"lang:default decode:true\">npm install nodemon --save-dev<\/pre>\n<p>En <code>nodemon<\/code> nos permitir\u00e1 recargar en caliente nuestro proyecto cada vez que cambiemos un archivo. No es un requisito, pero nos puede ahorrar algo de tiempo mientras estamos construyendo.<\/p>\n<p>Abra el\u00a0<strong>paquete.json<\/strong> y a\u00f1ada la siguiente secuencia de comandos:<\/p>\n<pre class=\"lang:default decode:true\">...\r\n\"scripts\": {\r\n    \"test\": \"echo \\\"Error: no test specified\\\" &amp;&amp; exit 1\",\r\n    \"start\": \".\/node_modules\/nodemon\/bin\/nodemon.js app.js\"\r\n},\r\n...<\/pre>\n<p>En este punto podemos iniciar el proceso de desarrollo de nuestra aplicaci\u00f3n.<\/p>\n<h2>Desarrollo de la base de datos y la l\u00f3gica Bitcoin<\/h2>\n<p>A la hora de desarrollar nuestra aplicaci\u00f3n, antes de empezar a preocuparnos por los puntos finales de la API, queremos crear nuestra base de datos y la l\u00f3gica relacionada con Bitcoin.<\/p>\n<p>Vamos a pasar nuestro tiempo en el proyecto de\u00a0<strong>clases\/helper.js<\/strong> . \u00c1bralo e incluya lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">const Couchbase = require(\"couchbase\");\r\nconst Request = require(\"request-promise\");\r\nconst UUID = require(\"uuid\");\r\nconst Bitcore = require(\"bitcore-lib\");\r\n\r\nclass Helper {\r\n\r\n    constructor(host, bucket, username, password, seed) {\r\n        this.cluster = new Couchbase.Cluster(\"couchbase:\/\/\" + host);\r\n        this.cluster.authenticate(username, password);\r\n        this.bucket = this.cluster.openBucket(bucket);\r\n        this.master = seed;\r\n    }\r\n\r\n    createKeyPair(account) { }\r\n\r\n    getWalletBalance(addresses) { }\r\n\r\n    getAddressBalance(address) { }\r\n\r\n    getAddressUtxo(address) { }\r\n\r\n    insert(data, id = UUID.v4()) { }\r\n\r\n    createAccount(data) { }\r\n\r\n    addAddress(account) { }\r\n\r\n    getAccountBalance(account) { }\r\n\r\n    getMasterAddresses() { }\r\n\r\n    getMasterKeyPairs() { }\r\n\r\n    getMasterAddressWithMinimum(addresses, amount) { }\r\n\r\n    getMasterChangeAddress() { }\r\n\r\n    getAddresses(account) { }\r\n\r\n    getPrivateKeyFromAddress(account, address) { }\r\n\r\n    createTransactionFromAccount(account, source, destination, amount) { }\r\n\r\n    createTransactionFromMaster(account, destination, amount) { }\r\n\r\n}\r\n\r\nmodule.exports = Helper;<\/pre>\n<p>Vamos a pasar esta clase como un singleton para nuestra aplicaci\u00f3n. En el <code>constructor<\/code> establecemos una conexi\u00f3n con nuestro cl\u00faster de base de datos, abrimos un Bucket y nos autenticamos. El Bucket abierto se utilizar\u00e1 a lo largo de esta clase de ayuda.<\/p>\n<p>Acabemos con la l\u00f3gica de Bitcoin antes que la de la base de datos.<\/p>\n<p>Si no est\u00e1s familiarizado con los monederos HD, son esencialmente un monedero derivado de una \u00fanica semilla. Usando la semilla puedes derivar hijos y esos hijos pueden tener hijos, y as\u00ed sucesivamente.<\/p>\n<pre class=\"lang:default decode:true\">createKeyPair(account) {\r\n    var account = this.master.deriveChild(account);\r\n    var key = account.deriveChild(Math.random() * 10000 + 1);\r\n    return { \"secret\": key.privateKey.toWIF().toString(), \"address\": key.privateKey.toAddress().toString() }\r\n}<\/pre>\n<p>En <code>maestro<\/code> variable en el <code>createKeyPair<\/code> representa la clave semilla de nivel superior. Cada cuenta de usuario ser\u00e1 un hijo directo de esa clave, de ah\u00ed que estemos derivando un hijo basado en una funci\u00f3n <code>cuenta<\/code> valor. En <code>cuenta<\/code> es un n\u00famero de persona y cada cuenta creada obtendr\u00e1 un n\u00famero incremental. Sin embargo, no vamos a generar claves de cuenta y darlo por terminado. En su lugar, cada clave de cuenta tendr\u00e1 10.000 posibles claves privadas y p\u00fablicas en caso de que no quieran usar la misma clave m\u00e1s de una vez. Una vez que hemos generado una clave al azar, la devolvemos.<\/p>\n<p>Del mismo modo, tenemos un <code>getMasterChangeAddress<\/code> como la siguiente:<\/p>\n<pre class=\"lang:default decode:true\">getMasterChangeAddress() {\r\n    var account = this.master.deriveChild(0);\r\n    var key = account.deriveChild(Math.random() * 10 + 1);\r\n    return { \"secret\": key.privateKey.toWIF().toString(), \"address\": key.privateKey.toAddress().toString() }\r\n}<\/pre>\n<p>Cuando empecemos a crear cuentas, empezar\u00e1n en uno, dejando cero para el intercambio o la aplicaci\u00f3n web, o como quieras llamarlo. Tambi\u00e9n estamos asignando 10 posibles direcciones a esta cuenta. Estas direcciones har\u00e1n dos cosas posibles. La primera es que guardar\u00e1n Bitcoin para transferir a otras cuentas y la segunda es que recibir\u00e1n pagos restantes, tambi\u00e9n conocido como el cambio. Recuerde, en una transacci\u00f3n Bitcoin, todo el producto de la transacci\u00f3n no gastado (UTXO) debe ser gastado, incluso si es menor que la cantidad deseada. Esto significa que la cantidad deseada se env\u00eda al destino y el resto se devuelve a una de estas 10 direcciones.<\/p>\n<p>\u00bfHay otras formas o formas mejores de hacerlo? Por supuesto, pero \u00e9sta servir\u00e1 para el ejemplo.<\/p>\n<p>Para obtener el saldo de cualquier direcci\u00f3n que utilicemos o generemos utilizando la semilla HD, podemos utilizar un explorador p\u00fablico de Bitcoin:<\/p>\n<pre class=\"lang:default decode:true\">getAddressBalance(address) {\r\n    return Request(\"https:\/\/insight.bitpay.com\/api\/addr\/\" + address);\r\n}<\/pre>\n<p>La funci\u00f3n anterior tomar\u00e1 una direcci\u00f3n y obtendr\u00e1 el saldo tanto en formato decimal como en satoshis. En adelante, el valor satoshi es el \u00fanico valor relevante para nosotros. Si tenemos X n\u00famero de direcciones para una cuenta dada, podemos obtener el saldo total utilizando una funci\u00f3n como esta:<\/p>\n<pre class=\"lang:default decode:true\">getWalletBalance(addresses) {\r\n    var promises = [];\r\n    for(var i = 0; i &lt; addresses.length; i++) {\r\n        promises.push(Request(\"https:\/\/insight.bitpay.com\/api\/addr\/\" + addresses[i]));\r\n    }\r\n    return Promise.all(promises).then(result =&gt; {\r\n        var balance = result.reduce((a, b) =&gt; a + JSON.parse(b).balanceSat, 0);\r\n        return new Promise((resolve, reject) =&gt; {\r\n            resolve({ \"balance\": balance });\r\n        });\r\n    });\r\n}<\/pre>\n<p>En el <code>getWalletBalance<\/code> funci\u00f3n estamos haciendo una solicitud para cada direcci\u00f3n y cuando todos han completado, podemos a\u00f1adir los saldos y devolverlos.<\/p>\n<p>Se necesita algo m\u00e1s que el saldo de una direcci\u00f3n para poder transferir criptomoneda. En su lugar, necesitamos conocer la salida de transacci\u00f3n no gastada (UTXO) para una direcci\u00f3n dada. Esto se puede encontrar utilizando la misma API de BitPay:<\/p>\n<pre class=\"lang:default decode:true\">getAddressUtxo(address) {\r\n    return Request(\"https:\/\/insight.bitpay.com\/api\/addr\/\" + address + \"\/utxo\").then(utxo =&gt; {\r\n        return new Promise((resolve, reject) =&gt; {\r\n            if(JSON.parse(utxo).length == 0) {\r\n                reject({ \"message\": \"There are no unspent transactions available.\" });\r\n            }\r\n            resolve(JSON.parse(utxo));\r\n        });\r\n    });\r\n}<\/pre>\n<p>Si no hay salida de transacci\u00f3n no gastada, significa que no hay nada que podamos transferir y deber\u00edamos lanzar un error en su lugar. Tener suficiente para transferir es otra historia.<\/p>\n<p>Por ejemplo, podr\u00edamos hacer algo as\u00ed:<\/p>\n<pre class=\"lang:default decode:true\">getMasterAddressWithMinimum(addresses, amount) {\r\n    var promises = [];\r\n    for(var i = 0; i &lt; addresses.length; i++) {\r\n        promises.push(Request(\"https:\/\/insight.bitpay.com\/api\/addr\/\" + addresses[i]));\r\n    }\r\n    return Promise.all(promises).then(result =&gt; {\r\n        for(var i = 0; i &lt; result.length; i++) {\r\n            if(result[i].balanceSat &gt;= amount) {\r\n                return resolve({ \"address\": result[i].addrStr });\r\n            }\r\n        }\r\n        reject({ \"message\": \"Not enough funds in exchange\" });\r\n    });\r\n}<\/pre>\n<p>En la funci\u00f3n anterior, estamos tomando una lista de direcciones y comprobando cu\u00e1l de ellas tiene una cantidad superior al umbral que proporcionamos. Si ninguna de ellas tiene saldo suficiente, probablemente deber\u00edamos transmitir ese mensaje.<\/p>\n<p>La \u00faltima funci\u00f3n relacionada con la utilidad es algo que ya hemos visto:<\/p>\n<pre class=\"lang:default decode:true\">getMasterKeyPairs() {\r\n    var keypairs = [];\r\n    var key;\r\n    var account = this.master.deriveChild(0);\r\n    for(var i = 1; i &lt;= 10; i++) {\r\n        key = account.deriveChild(i);\r\n        keypairs.push({ \"secret\": key.privateKey.toWIF().toString(), \"address\": key.privateKey.toAddress().toString() });\r\n    }\r\n    return keypairs;\r\n}<\/pre>\n<p>La funci\u00f3n anterior nos obtendr\u00e1 todas las claves maestras, que ser\u00e1n \u00fatiles para firmar y comprobar el valor.<\/p>\n<p>S\u00f3lo para reiterar, estoy usando un valor finito para el n\u00famero de claves que se generan. Usted puede o no puede querer hacer lo mismo, depende de usted.<\/p>\n<p>Ahora vamos a sumergirnos en algo de l\u00f3gica NoSQL para almacenar los datos de nuestra aplicaci\u00f3n.<\/p>\n<p>En este momento no hay datos en nuestra base de datos. El primer paso l\u00f3gico podr\u00eda ser crear algunos datos. Aunque no es particularmente dif\u00edcil de forma independiente, podemos crear una funci\u00f3n como esta:<\/p>\n<pre class=\"lang:default decode:true\">insert(data, id = UUID.v4()) {\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.insert(id, data, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            data.id = id;\r\n            resolve(data);\r\n        });\r\n    });\r\n}<\/pre>\n<p>Esencialmente, estamos aceptando un objeto y un id para ser utilizado como una clave de documento. Si no se proporciona una clave de documento, la generaremos autom\u00e1ticamente. Cuando todo est\u00e9 dicho y hecho, devolveremos lo que fue creado incluyendo el id en la respuesta.<\/p>\n<p>Digamos que queremos crear una cuenta de usuario. Podemos hacer lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">createAccount(data) {\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.counter(\"accounts::total\", 1, { \"initial\": 1 }, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            data.account = result.value;\r\n            this.insert(data).then(result =&gt; {\r\n                resolve(result);\r\n            }, error =&gt; {\r\n                reject(error);\r\n            });\r\n        });\r\n    });\r\n}<\/pre>\n<p>Recuerde, las cuentas son manejadas por un valor num\u00e9rico auto incremental para este ejemplo. Podemos crear valores incrementales utilizando un <code>contador<\/code> en Couchbase. Si el contador no existe, lo inicializaremos a 1 y lo incrementaremos en cada siguiente llamada. Recuerda, 0 est\u00e1 reservado para claves de aplicaci\u00f3n.<\/p>\n<p>Despu\u00e9s de obtener el valor de nuestro contador, lo a\u00f1adimos al objeto pasado y llamamos a nuestra funci\u00f3n de inserci\u00f3n, que en este caso genera un identificador \u00fanico para nosotros.<\/p>\n<p>Todav\u00eda no lo hemos visto porque no tenemos ning\u00fan endpoint, pero vamos a suponer que cuando creamos una cuenta, no tiene informaci\u00f3n de direcci\u00f3n, s\u00f3lo un identificador de cuenta. Podr\u00edamos querer a\u00f1adir una direcci\u00f3n para el usuario:<\/p>\n<pre class=\"lang:default decode:true\">addAddress(account) {\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.get(account, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            var keypair = this.createKeyPair(result.value.account);\r\n            this.bucket.mutateIn(account).arrayAppend(\"addresses\", keypair, true).execute((error, result) =&gt; {\r\n                if(error) {\r\n                    reject({ \"code\": error.code, \"message\": error.message });\r\n                }\r\n                resolve({ \"address\": keypair.address });\r\n            });\r\n        });\r\n    });\r\n}<\/pre>\n<p>Al a\u00f1adir una direcci\u00f3n, primero obtenemos el usuario por el id del documento. Cuando se recupera el documento, obtenemos el valor num\u00e9rico de la cuenta y creamos un nuevo par de claves de nuestras 10.000 opciones. Utilizando un <a href=\"https:\/\/www.couchbase.com\/blog\/es\/subdoc-explained\/\">subdocumento<\/a> podemos a\u00f1adir el par de claves al documento de usuario sin tener que descargar el documento ni manipularlo.<\/p>\n<p>Hay que tener en cuenta algo muy serio sobre lo que acabamos de hacer.<\/p>\n<p>Estoy almacenando la clave privada sin cifrar y la direcci\u00f3n p\u00fablica en el documento de usuario. Esto es un gran no-no para la producci\u00f3n. \u00bfRecuerdas todas esas historias que has le\u00eddo sobre gente a la que le han robado sus claves? En realidad, querr\u00edamos cifrar los datos antes de insertarlos. Podemos hacerlo usando la librer\u00eda crypto de Node.js, o si estamos usando Couchbase Server 5.5, el SDK de Node.js para Couchbase ofrece encriptaci\u00f3n. Sin embargo, no lo exploraremos aqu\u00ed.<\/p>\n<p>Bien, ya tenemos los datos de las cuentas y las direcciones en la base de datos. Vamos a consultar esos datos:<\/p>\n<pre class=\"lang:default decode:true\">getAddresses(account) {\r\n    var statement, params;\r\n    if(account) {\r\n        statement = \"SELECT VALUE addresses.address FROM \" + this.bucket._name + \" AS account USE KEYS $id UNNEST account.addresses as addresses\";\r\n        params = { \"id\": account };\r\n    } else {\r\n        statement = \"SELECT VALUE addresses.address FROM \" + this.bucket._name + \" AS account UNNEST account.addresses as addresses WHERE account.type = 'account'\";\r\n    }\r\n    var query = Couchbase.N1qlQuery.fromString(statement);\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.query(query, params, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            resolve(result);\r\n        });\r\n    });\r\n}<\/pre>\n<p>Lo anterior <code>getAddresses<\/code> puede hacer una de dos cosas. Si se proporcion\u00f3 una cuenta, utilizaremos una consulta N1QL para obtener todas las direcciones de esa cuenta en particular. Si no se proporcion\u00f3 ninguna cuenta, obtendremos todas las direcciones de todas las cuentas de la base de datos. En ambos casos, s\u00f3lo obtendremos las direcciones p\u00fablicas, nada confidencial. Usando una consulta N1QL parametrizada, podemos devolver los resultados de la base de datos al cliente.<\/p>\n<p>Algo a tener en cuenta en nuestra consulta.<\/p>\n<p>Estamos almacenando nuestras direcciones en un array en los documentos de usuario. Utilizando un <code>UNNEST<\/code> podemos aplanar esas direcciones y hacer que la respuesta sea m\u00e1s atractiva.<\/p>\n<p>Supongamos ahora que tenemos una direcci\u00f3n y queremos obtener la clave privada correspondiente. Podr\u00edamos hacer lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">getPrivateKeyFromAddress(account, address) {\r\n    var statement = \"SELECT VALUE keypairs.secret FROM \" + this.bucket._name + \" AS account USE KEYS $account UNNEST account.addresses AS keypairs WHERE keypairs.address = $address\";\r\n    var query = Couchbase.N1qlQuery.fromString(statement);\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.query(query, { \"account\": account, \"address\": address }, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            resolve({ \"secret\": result[0] });\r\n        });\r\n    });\r\n}<\/pre>\n<p>Dada una cuenta concreta, creamos una consulta similar a la que vimos anteriormente. Esta vez, despu\u00e9s de <code>UNNEST<\/code>hacemos un <code>DONDE<\/code> para obtener resultados s\u00f3lo para la direcci\u00f3n coincidente. Si quisi\u00e9ramos podr\u00edamos haber hecho una operaci\u00f3n de array en su lugar. Con Couchbase y N1QL, hay muchas maneras de resolver un problema.<\/p>\n<p>Vamos a cambiar un poco de marcha aqu\u00ed. Hasta ahora hemos hecho operaciones orientadas a cuentas en nuestra base de datos NoSQL. Otro aspecto importante son las transacciones. Por ejemplo, tal vez el usuario X deposita algunos USD moneda para BTC y el usuario Y hace una retirada. Necesitamos almacenar y consultar la informaci\u00f3n de esa transacci\u00f3n.<\/p>\n<p>Las funciones del punto final de la API guardar\u00e1n los datos de la transacci\u00f3n, pero a\u00fan podremos consultarlos.<\/p>\n<pre class=\"lang:default decode:true\">getAccountBalance(account) {\r\n    var statement = \"SELECT SUM(tx.satoshis) AS balance FROM \" + this.bucket._name + \" AS tx WHERE tx.type = 'transaction' AND tx.account = $account\";\r\n    var query = Couchbase.N1qlQuery.fromString(statement);\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.bucket.query(query, { \"account\": account }, (error, result) =&gt; {\r\n            if(error) {\r\n                reject({ \"code\": error.code, \"message\": error.message });\r\n            }\r\n            resolve({ \"balance\": result[0].balance });\r\n        });\r\n    });\r\n}<\/pre>\n<p>Dada una cuenta, queremos obtener el saldo de la cuenta de un usuario concreto.<\/p>\n<p>Espera un segundo, demos un paso atr\u00e1s porque \u00bfno hab\u00edamos creado ya algunas funciones de saldo de cuenta? T\u00e9cnicamente s\u00ed, pero esas funciones eran para comprobar el saldo del monedero, no el saldo de la cuenta.<\/p>\n<p>Aqu\u00ed es donde parte de mi experiencia se convierte en zona gris. Cada vez que transfieres Bitcoin, hay una comisi\u00f3n implicada, y a veces es bastante cara. Cuando haces un dep\u00f3sito, no es rentable transferir dinero a tu monedero porque te cobrar\u00edan una tasa de minero. Luego se le cobrar\u00eda por retirar e incluso transferir de nuevo. En ese momento ya ha perdido la mayor parte de su Bitcoin.<\/p>\n<p>En su lugar, creo que las bolsas tienen una cuenta de haberes similar a una cuenta del mercado monetario burs\u00e1til. Hay un registro del dinero que deber\u00edas tener en tu cuenta, pero t\u00e9cnicamente no est\u00e1 en un monedero. Cuando quieres transferir, est\u00e1s transfiriendo desde la direcci\u00f3n de la aplicaci\u00f3n, no desde tu direcci\u00f3n de usuario. Cuando usted retira, s\u00f3lo se est\u00e1 restando.<\/p>\n<p>De nuevo, no s\u00e9 si realmente funciona as\u00ed, pero es como yo lo har\u00eda para evitar comisiones por doquier.<\/p>\n<p>Volviendo a nuestro <code>getAccountBalance<\/code> funci\u00f3n. Tomamos una suma de cada transacci\u00f3n. Los dep\u00f3sitos tienen un valor positivo, mientras que las transferencias y las retiradas tienen un valor negativo. La suma de esta informaci\u00f3n deber\u00eda darte un n\u00famero exacto, excluyendo el saldo de tu monedero. M\u00e1s adelante obtendremos una cuenta con el saldo del monedero.<\/p>\n<p>Dado lo poco que sabemos sobre los saldos de las cuentas, podemos intentar crear una transacci\u00f3n desde nuestro monedero:<\/p>\n<pre class=\"lang:default decode:true\">createTransactionFromAccount(account, source, destination, amount) {\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.getAddressBalance(source).then(sourceAddress =&gt; {\r\n            if(sourceAddress.balanceSat &lt; amount) {\r\n                return reject({ \"message\": \"Not enough funds in account.\" });\r\n            }\r\n            this.getPrivateKeyFromAddress(account, source).then(keypair =&gt; {\r\n                this.getAddressUtxo(source).then(utxo =&gt; {\r\n                    var transaction = new Bitcore.Transaction();\r\n                    for(var i = 0; i &lt; utxo.length; i++) {\r\n                        transaction.from(utxo[i]);\r\n                    }\r\n                    transaction.to(destination, amount);\r\n                    this.addAddress(account).then(change =&gt; {\r\n                        transaction.change(change.address);\r\n                        transaction.sign(keypair.secret);\r\n                        resolve(transaction);\r\n                    }, error =&gt; reject(error));\r\n                }, error =&gt; reject(error));\r\n            }, error =&gt; reject(error));\r\n        }, error =&gt; reject(error));\r\n    });\r\n}<\/pre>\n<p>Si se nos proporciona una direcci\u00f3n de origen, una direcci\u00f3n de destino y una cantidad, podemos crear y firmar una transacci\u00f3n que posteriormente se emitir\u00e1 en la red Bitcoin.<\/p>\n<p>Primero obtenemos el saldo de la direcci\u00f3n de origen en cuesti\u00f3n. Necesitamos asegurarnos de que tiene suficiente UTXO para cumplir con la expectativa de cantidad enviada. Tenga en cuenta que en este ejemplo, estamos haciendo transacciones de una sola direcci\u00f3n. Si quisieras complicarte, podr\u00edas enviar desde m\u00faltiples direcciones en una sola transacci\u00f3n. No vamos a hacer eso aqu\u00ed. Si nuestra \u00fanica direcci\u00f3n tiene fondos suficientes, obtenemos la clave privada para ella y los datos UTXO. Con los datos UTXO podemos crear una transacci\u00f3n Bitcoin, aplicar la direcci\u00f3n de destino y una direcci\u00f3n de cambio, luego firmar la transacci\u00f3n usando nuestra clave privada. La respuesta puede ser difundida.<\/p>\n<p>Del mismo modo, supongamos que queremos transferir Bitcoin desde nuestra cuenta de haberes:<\/p>\n<pre class=\"lang:default decode:true\">createTransactionFromMaster(account, destination, amount) {\r\n    return new Promise((resolve, reject) =&gt; {\r\n        this.getAccountBalance(account).then(accountBalance =&gt; {\r\n            if(accountBalance.balance &lt; amount) {\r\n                reject({ \"message\": \"Not enough funds in account.\" });\r\n            }\r\n            var mKeyPairs = this.getMasterKeyPairs();\r\n            var masterAddresses = mKeyPairs.map(a =&gt; a.address);\r\n            this.getMasterAddressWithMinimum(masterAddresses, amount).then(funds =&gt; {\r\n                this.getAddressUtxo(funds.address).then(utxo =&gt; {\r\n                    var transaction = new Bitcore.Transaction();\r\n                    for(var i = 0; i &lt; utxo.length; i++) {\r\n                        transaction.from(utxo[i]);\r\n                    }\r\n                    transaction.to(destination, amount);\r\n                    var change = helper.getMasterChangeAddress();\r\n                    transaction.change(change.address);\r\n                    for(var j = 0; j &lt; mKeyPairs.length; j ++) {\r\n                        if(mKeyPairs[j].address == funds.address) {\r\n                            transaction.sign(mKeyPairs[j].secret);\r\n                        }\r\n                    }\r\n                    var tx = {\r\n                        account: account,\r\n                        satoshis: (amount * -1),\r\n                        timestamp: (new Date()).getTime(),\r\n                        status: \"transfer\",\r\n                        type: \"transaction\"\r\n                    };\r\n                    this.insert(tx).then(result =&gt; {\r\n                        resolve(transaction);\r\n                    }, error =&gt; reject(error));\r\n                }, error =&gt; reject(error));\r\n            }, error =&gt; reject(error));\r\n        }, error =&gt; reject(error));\r\n    });\r\n}<\/pre>\n<p>Suponemos que nuestras direcciones de intercambio se han cargado con una cantidad insana de Bitcoin para satisfacer la demanda.<\/p>\n<p>El primer paso es asegurarnos de que tenemos fondos en nuestra cuenta de haberes. Podemos ejecutar esa consulta que suma cada una de nuestras transacciones para obtener un n\u00famero v\u00e1lido. Si tenemos suficiente, podemos obtener nuestros 10 pares de claves maestras y las direcciones. Tenemos que comprobar qu\u00e9 direcci\u00f3n tiene fondos suficientes para enviar. Recuerde, las transacciones de una sola direcci\u00f3n aqu\u00ed, cuando podr\u00eda haber m\u00e1s.<\/p>\n<p>Si una direcci\u00f3n tiene fondos suficientes, obtenemos los datos UTXO y comenzamos a realizar una transacci\u00f3n. Esta vez, en lugar de nuestra cartera como direcci\u00f3n de origen, utilizamos la cartera del intercambio. Despu\u00e9s de obtener una transacci\u00f3n firmada, queremos crear una transacci\u00f3n en nuestra base de datos para restar el valor que estamos transfiriendo.<\/p>\n<p>Antes de pasar a los puntos finales de la API, quiero reiterar algunas cosas:<\/p>\n<ul>\n<li>Supongo que los intercambios populares tienen una cuenta de retenci\u00f3n para evitar las tasas impuestas a las direcciones de los monederos.<\/li>\n<li>En este ejemplo estamos utilizando transacciones de una sola direcci\u00f3n, en lugar de agregar las que tenemos.<\/li>\n<li>No estoy cifrando los datos clave de los documentos de la cuenta, cuando deber\u00eda hacerlo.<\/li>\n<li>No estoy emitiendo ninguna transacci\u00f3n, s\u00f3lo cre\u00e1ndolas.<\/li>\n<\/ul>\n<p>Ahora vamos a centrarnos en nuestros puntos finales de la API, la parte sencilla.<\/p>\n<h2>Dise\u00f1o de puntos finales de API RESTful con Express Framework<\/h2>\n<p>Recuerde, como configuramos al principio, nuestros puntos finales se dividir\u00e1n en tres archivos que act\u00faan como agrupaciones. Empezaremos con el grupo m\u00e1s peque\u00f1o y simple de endpoints, que son m\u00e1s para utilidad que otra cosa.<\/p>\n<p>Abra el archivo\u00a0<strong>rutas\/utilidad.js<\/strong> e incluya lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">const Bitcore = require(\"bitcore-lib\");\r\nconst Mnemonic = require(\"bitcore-mnemonic\");\r\n\r\nmodule.exports = (app) =&gt; {\r\n\r\n    app.get(\"\/mnemonic\", (request, response) =&gt; {\r\n        response.send({\r\n            \"mnemonic\": (new Mnemonic(Mnemonic.Words.ENGLISH)).toString()\r\n        });\r\n    });\r\n\r\n    app.get(\"\/balance\/value\", (request, response) =&gt; {\r\n        Request(\"https:\/\/api.coinmarketcap.com\/v1\/ticker\/bitcoin\/\").then(market =&gt; {\r\n            response.send({ \"value\": \"$\" + (JSON.parse(market)[0].price_usd * request.query.balance).toFixed(2) });\r\n        }, error =&gt; {\r\n            response.status(500).send(error);\r\n        });\r\n    });\r\n\r\n}<\/pre>\n<p>Aqu\u00ed tenemos dos puntos finales, uno para generar semillas mnem\u00f3nicas y el otro para obtener el valor fiat de un saldo Bitcoin. Ninguno de los dos son realmente necesarios, pero en el primer lanzamiento, podr\u00eda ser bueno generar un valor semilla para guardarlo m\u00e1s tarde en nuestro archivo de configuraci\u00f3n.<\/p>\n<p>Ahora abra el proyecto\u00a0<strong>rutas\/cuenta.js<\/strong> para que podamos manejar la informaci\u00f3n de la cuenta:<\/p>\n<pre class=\"lang:default decode:true\">const Request = require(\"request-promise\");\r\nconst Joi = require(\"joi\");\r\nconst helper = require(\"..\/app\").helper;\r\n\r\nmodule.exports = (app) =&gt; {\r\n\r\n    app.post(\"\/account\", (request, response) =&gt; { });\r\n\r\n    app.put(\"\/account\/address\/:id\", (request, response) =&gt; { });\r\n\r\n    app.get(\"\/account\/addresses\/:id\", (request, response) =&gt; { });\r\n\r\n    app.get(\"\/addresses\", (request, response) =&gt; { });\r\n\r\n    app.get(\"\/account\/balance\/:id\", (request, response) =&gt; { });\r\n\r\n    app.get(\"\/address\/balance\/:id\", (request, response) =&gt; { });\r\n\r\n}<\/pre>\n<p>Tenga en cuenta que estamos tirando de la clase de ayuda de la\u00a0<strong>app.js<\/strong> que a\u00fan no hemos empezado. S\u00f3lo tienes que ir con \u00e9l por ahora y tendr\u00e1 sentido m\u00e1s tarde, aunque no es nada especial.<\/p>\n<p>A la hora de crear cuentas, disponemos de lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">app.post(\"\/account\", (request, response) =&gt; {\r\n    var model = Joi.object().keys({\r\n        firstname: Joi.string().required(),\r\n        lastname: Joi.string().required(),\r\n        type: Joi.string().forbidden().default(\"account\")\r\n    });\r\n    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) =&gt; {\r\n        if(error) {\r\n            return response.status(500).send(error);\r\n        }\r\n        helper.createAccount(value).then(result =&gt; {\r\n            response.send(value);\r\n        }, error =&gt; {\r\n            response.status(500).send(error);\r\n        });\r\n    });\r\n});<\/pre>\n<p>Usando Joi podemos validar el cuerpo de la petici\u00f3n y lanzar errores si no es correcto. Asumiendo que el cuerpo de la petici\u00f3n es correcto, podemos llamar a nuestro <code>crearCuenta<\/code> para guardar una nueva cuenta en la base de datos.<\/p>\n<p>Con una cuenta creada, podemos a\u00f1adir algunas direcciones:<\/p>\n<pre class=\"lang:default decode:true\">app.put(\"\/account\/address\/:id\", (request, response) =&gt; {\r\n    helper.addAddress(request.params.id).then(result =&gt; {\r\n        response.send(result);\r\n    }, error =&gt; {\r\n        return response.status(500).send(error);\r\n    });\r\n});<\/pre>\n<p>Utilizando el identificador de cuenta que se pas\u00f3, podemos llamar a nuestro <code>addAddress<\/code> para utilizar una operaci\u00f3n de subdocumento en nuestro documento.<\/p>\n<p>No est\u00e1 tan mal, \u00bfverdad?<\/p>\n<p>Para obtener todas las direcciones de una cuenta en particular, podr\u00edamos tener algo como lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">app.get(\"\/account\/addresses\/:id\", (request, response) =&gt; {\r\n    helper.getAddresses(request.params.id).then(result =&gt; {\r\n        response.send(result);\r\n    }, error =&gt; {\r\n        response.status(500).send(error);\r\n    });\r\n});<\/pre>\n<p>Alternativamente, si no proporcionamos un id, podemos obtener todas las direcciones de todas las cuentas utilizando la siguiente funci\u00f3n endpoint:<\/p>\n<pre class=\"lang:default decode:true\">app.get(\"\/addresses\", (request, response) =&gt; {\r\n    helper.getAddresses().then(result =&gt; {\r\n        response.send(result);\r\n    }, error =&gt; {\r\n        response.status(500).send(error);\r\n    });\r\n});<\/pre>\n<p>Ahora probablemente la funci\u00f3n m\u00e1s complicada. Digamos que queremos obtener el saldo de nuestra cuenta, que incluye la cuenta de explotaci\u00f3n, as\u00ed como cada una de nuestras direcciones de cartera. Podemos hacer lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">app.get(\"\/account\/balance\/:id\", (request, response) =&gt; {\r\n    helper.getAddresses(request.params.id).then(addresses =&gt; helper.getWalletBalance(addresses)).then(balance =&gt; {\r\n        helper.getAccountBalance(request.params.id).then(result =&gt; {\r\n            response.send({ \"balance\": balance.balance + result.balance });\r\n        }, error =&gt; {\r\n            response.status(500).send({ \"code\": error.code, \"message\": error.message });\r\n        });\r\n    }, error =&gt; {\r\n        response.status(500).send({ \"code\": error.code, \"message\": error.message });\r\n    });\r\n});<\/pre>\n<p>Lo anterior llamar\u00e1 a nuestras dos funciones para obtener el balance, y sumar\u00e1 los resultados para obtener un balance masivo.<\/p>\n<p>Los puntos finales de las cuentas no eran especialmente interesantes. La creaci\u00f3n de transacciones es un poco m\u00e1s emocionante.<\/p>\n<p>Abra el archivo\u00a0<strong>rutas\/transacci\u00f3n.js<\/strong> e incluya lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">const Request = require(\"request-promise\");\r\nconst Joi = require(\"joi\");\r\nconst Bitcore = require(\"bitcore-lib\");\r\nconst helper = require(\"..\/app\").helper;\r\n\r\nmodule.exports = (app) =&gt; {\r\n\r\n    app.post(\"\/withdraw\", (request, response) =&gt; { });\r\n\r\n    app.post(\"\/deposit\", (request, response) =&gt; { });\r\n\r\n    app.post(\"\/transfer\", (request, response) =&gt; { });\r\n\r\n}<\/pre>\n<p>Tenemos tres tipos diferentes de transacci\u00f3n. Podemos depositar moneda fiduciaria por Bitcoin, retirar Bitcoin por moneda fiduciaria y transferir Bitcoin a nuevas direcciones de monedero.<\/p>\n<p>Echemos un vistazo al punto final de dep\u00f3sito:<\/p>\n<pre class=\"lang:default decode:true\">app.post(\"\/deposit\", (request, response) =&gt; {\r\n    var model = Joi.object().keys({\r\n        usd: Joi.number().required(),\r\n        id: Joi.string().required()\r\n    });\r\n    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) =&gt; {\r\n        if(error) {\r\n            return response.status(500).send(error);\r\n        }\r\n        Request(\"https:\/\/api.coinmarketcap.com\/v1\/ticker\/bitcoin\/\").then(market =&gt; {\r\n            var btc = value.usd \/ JSON.parse(market)[0].price_usd;\r\n            var transaction = {\r\n                account: value.id,\r\n                usd: value.usd,\r\n                satoshis: Bitcore.Unit.fromBTC(btc).toSatoshis(),\r\n                timestamp: (new Date()).getTime(),\r\n                status: \"deposit\",\r\n                type: \"transaction\"\r\n            };\r\n            helper.insert(transaction).then(result =&gt; {\r\n                response.send(result);\r\n            }, error =&gt; {\r\n                response.status(500).send(error);\r\n            });\r\n        }, error =&gt; {\r\n            response.status(500).send(error);\r\n        });\r\n    });\r\n});<\/pre>\n<p>Despu\u00e9s de validar la entrada, comprobamos el valor actual de Bitcoin en USD con CoinMarketCap. Utilizando los datos de la respuesta, podemos calcular cu\u00e1ntos Bitcoin deber\u00edamos obtener en funci\u00f3n de la cantidad depositada en USD.<\/p>\n<p>Despu\u00e9s de crear una transacci\u00f3n en la base de datos, podemos guardarla y, como es un n\u00famero positivo, volver\u00e1 como saldo positivo al consultarla.<\/p>\n<p>Ahora digamos que queremos retirar dinero de nuestro Bitcoin:<\/p>\n<pre class=\"lang:default decode:true\">app.post(\"\/withdraw\", (request, response) =&gt; {\r\n    var model = Joi.object().keys({\r\n        satoshis: Joi.number().required(),\r\n        id: Joi.string().required()\r\n    });\r\n    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) =&gt; {\r\n        if(error) {\r\n            return response.status(500).send(error);\r\n        }\r\n        helper.getAccountBalance(value.id).then(result =&gt; {\r\n            if(result.balance == null || (result.balance - value.satoshis) &lt; 0) {\r\n                return response.status(500).send({ \"message\": \"There are not `\" + value.satoshis + \"` satoshis available for withdrawal\" });\r\n            }\r\n            Request(\"https:\/\/api.coinmarketcap.com\/v1\/ticker\/bitcoin\/\").then(market =&gt; {\r\n                var usd = (Bitcore.Unit.fromSatoshis(value.satoshis).toBTC() * JSON.parse(market)[0].price_usd).toFixed(2);\r\n                var transaction = {\r\n                    account: value.id,\r\n                    satoshis: (value.satoshis * -1),\r\n                    usd: parseFloat(usd),\r\n                    timestamp: (new Date()).getTime(),\r\n                    status: \"withdrawal\",\r\n                    type: \"transaction\"\r\n                };\r\n                helper.insert(transaction).then(result =&gt; {\r\n                    response.send(result);\r\n                }, error =&gt; {\r\n                    response.status(500).send(error);\r\n                });\r\n            }, error =&gt; {\r\n                response.status(500).send(error);\r\n            });\r\n        }, error =&gt; {\r\n            return response.status(500).send(error);\r\n        });\r\n    });\r\n});<\/pre>\n<p>Aqu\u00ed ocurren eventos similares. Despu\u00e9s de validar el cuerpo de la solicitud, obtenemos el saldo de nuestra cuenta y nos aseguramos de que la cantidad que estamos retirando es menor o igual a nuestro saldo. Si lo es, podemos hacer otra conversi\u00f3n basada en el precio actual de CoinMarketCap. Crearemos una transacci\u00f3n usando un valor negativo y la guardaremos en la base de datos.<\/p>\n<p>En ambos casos estamos confiando en CoinMarketCap que ha tenido controversia negativa en el pasado. Es posible que desee elegir un recurso diferente para las conversiones.<\/p>\n<p>Por \u00faltimo, tenemos las transferencias:<\/p>\n<pre class=\"lang:default decode:true\">app.post(\"\/transfer\", (request, response) =&gt; {\r\n    var model = Joi.object().keys({\r\n        amount: Joi.number().required(),\r\n        sourceaddress: Joi.string().optional(),\r\n        destinationaddress: Joi.string().required(),\r\n        id: Joi.string().required()\r\n    });\r\n    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) =&gt; {\r\n        if(error) {\r\n            return response.status(500).send(error);\r\n        }\r\n        if(value.sourceaddress) {\r\n            helper.createTransactionFromAccount(value.id, value.sourceaddress, value.destinationaddress, value.amount).then(result =&gt; {\r\n                response.send(result);\r\n            }, error =&gt; {\r\n                response.status(500).send(error);\r\n            });\r\n        } else {\r\n            helper.createTransactionFromMaster(value.id, value.destinationaddress, value.amount).then(result =&gt; {\r\n                response.send(result);\r\n            }, error =&gt; {\r\n                response.status(500).send(error);\r\n            });\r\n        }\r\n    });\r\n});<\/pre>\n<p>Si la solicitud contiene una direcci\u00f3n de origen, vamos a transferir desde nuestro propio monedero, de lo contrario vamos a transferir desde el monedero que gestiona la bolsa.<\/p>\n<p>Todo esto se basa en funciones que hab\u00edamos creado previamente.<\/p>\n<p>Con los puntos finales fuera del camino, podemos centrarnos en bootstrapping nuestra aplicaci\u00f3n y llegar a una conclusi\u00f3n.<\/p>\n<h2>Arranque de la aplicaci\u00f3n Express Framework<\/h2>\n<p>En este momento tenemos dos archivos que permanecen intactos por el ejemplo. No hemos a\u00f1adido una configuraci\u00f3n o l\u00f3gica de manejo para arrancar nuestros endpoints.<\/p>\n<p>Abra el archivo\u00a0<strong>config.json<\/strong> e incluya algo como lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">{\r\n    \"mnemonic\": \"manage inspire agent october potato thought hospital trim shoulder round tired kangaroo\",\r\n    \"host\": \"localhost\",\r\n    \"bucket\": \"bitbase\",\r\n    \"username\": \"bitbase\",\r\n    \"password\": \"123456\"\r\n}<\/pre>\n<p>Recuerde que este archivo es incre\u00edblemente sensible. Considera bloquearlo o incluso utilizar un enfoque diferente. Si la semilla est\u00e1 expuesta, cada clave privada para todas las cuentas de usuario y la cuenta de intercambio se puede obtener con cero esfuerzo.<\/p>\n<p>Ahora abra el proyecto\u00a0<strong>app.js<\/strong> e incluya lo siguiente:<\/p>\n<pre class=\"lang:default decode:true\">const Express = require(\"express\");\r\nconst BodyParser = require(\"body-parser\");\r\nconst Bitcore = require(\"bitcore-lib\");\r\nconst Mnemonic = require(\"bitcore-mnemonic\");\r\nconst Config = require(\".\/config\");\r\nconst Helper = require(\".\/classes\/helper\");\r\n\r\nvar app = Express();\r\n\r\napp.use(BodyParser.json());\r\napp.use(BodyParser.urlencoded({ extended: true }));\r\n\r\nvar mnemonic = new Mnemonic(Config.mnemonic);\r\nvar master = new Bitcore.HDPrivateKey(mnemonic.toHDPrivateKey());\r\n\r\nmodule.exports.helper = new Helper(Config.host, Config.bucket, Config.username, Config.password, master);\r\n\r\nrequire(\".\/routes\/account.js\")(app);\r\nrequire(\".\/routes\/transaction.js\")(app);\r\nrequire(\".\/routes\/utility.js\")(app);\r\n\r\nvar server = app.listen(3000, () =&gt; {\r\n    console.log(\"Listening at :\" + server.address().port + \"...\");\r\n});<\/pre>\n<p>Lo que estamos haciendo es inicializar Express, cargar la informaci\u00f3n de configuraci\u00f3n y enlazar nuestras rutas. La p\u00e1gina <code>m\u00f3dulo.exports.helper<\/code> es nuestro singleton que se utilizar\u00e1 en todos los dem\u00e1s archivos JavaScript.<\/p>\n<h2>Conclusi\u00f3n<\/h2>\n<p>Acaba de ver c\u00f3mo empezar a crear su propia bolsa de criptomonedas <a href=\"https:\/\/www.couchbase.com\/blog\/es\/build-a-rest-api-with-node-js-express-and-couchbase\/\">usando Node.js<\/a> y <a href=\"https:\/\/www.couchbase.com\/blog\/es\/\" target=\"_blank\" rel=\"noopener noreferrer\">Couchbase<\/a> como base de datos NoSQL. Cubrimos mucho, desde la generaci\u00f3n de carteras HD hasta la creaci\u00f3n de endpoints con l\u00f3gica de base de datos compleja.<\/p>\n<p>Sin embargo, no me cansar\u00e9 de repetirlo. Soy un entusiasta de la criptomoneda y no tengo experiencia real en el espacio financiero. Las cosas que he compartido deber\u00edan funcionar, pero se pueden hacer mucho mejor. No se olvide de cifrar sus claves y mantener su semilla segura. Pruebe su trabajo y sepa en lo que se est\u00e1 metiendo.<\/p>\n<p>Si desea descargar este proyecto, cons\u00faltelo en <a href=\"https:\/\/github.com\/couchbaselabs\/couchbase-exchange\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub<\/a>. Si quieres compartir tu visi\u00f3n, experiencia, etc., sobre el tema, por favor, hazlo en los comentarios. La comunidad puede trabajar para crear algo grande.<\/p>\n<p>Si eres un fan de Golang, he creado un proyecto similar en un <a href=\"https:\/\/www.thepolyglotdeveloper.com\/2018\/03\/create-bitcoin-hardware-wallet-golang-raspberry-pi-zero\/\" target=\"_blank\" rel=\"noopener noreferrer\">tutorial anterior<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been following cryptocurrency-related subjects such as Bitcoin for a few months now and I&#8217;m very fascinated with everything that has been going on. As a web application developer, one topic that I&#8217;ve been particularly interested in learning more about [&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":[1815,1816,1822,1812],"tags":[2190,1725],"ppma_author":[9032],"class_list":["post-4892","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-best-practices-and-tutorials","category-couchbase-server","category-node-js","category-n1ql-query","tag-cryptocurrency","tag-nosql-database"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.2 (Yoast SEO v26.2) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Bitcoin Cryptocurrency Application with Node.js + NoSQL<\/title>\n<meta name=\"description\" content=\"Read this Couchbase post to see best practice examples of how to develop Bitcoin Cryptocurrency Applications using Node.js and the NoSQL database.\" \/>\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\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\" \/>\n<meta property=\"og:locale\" content=\"es_MX\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Developing a Bitcoin Cryptocurrency Application with Node.js and NoSQL\" \/>\n<meta property=\"og:description\" content=\"Read this Couchbase post to see best practice examples of how to develop Bitcoin Cryptocurrency Applications using Node.js and the NoSQL database.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/es\/developing-bitcoin-cryptocurrency-application-nodejs-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=\"2018-03-28T14:00:44+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T01:45:36+00:00\" \/>\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=\"16 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\"},\"author\":{\"name\":\"Nic Raboy, Developer Advocate, Couchbase\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1\"},\"headline\":\"Developing a Bitcoin Cryptocurrency Application with Node.js and NoSQL\",\"datePublished\":\"2018-03-28T14:00:44+00:00\",\"dateModified\":\"2025-06-14T01:45:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\"},\"wordCount\":3718,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"keywords\":[\"cryptocurrency\",\"NoSQL Database\"],\"articleSection\":[\"Best Practices and Tutorials\",\"Couchbase Server\",\"Node.js\",\"SQL++ \/ N1QL Query\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\",\"name\":\"Bitcoin Cryptocurrency Application with Node.js + NoSQL\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"datePublished\":\"2018-03-28T14:00:44+00:00\",\"dateModified\":\"2025-06-14T01:45:36+00:00\",\"description\":\"Read this Couchbase post to see best practice examples of how to develop Bitcoin Cryptocurrency Applications using Node.js and the NoSQL database.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-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\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Developing a Bitcoin Cryptocurrency Application with Node.js and 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":"Bitcoin Cryptocurrency Application with Node.js + NoSQL","description":"Lee este post de Couchbase para ver ejemplos de mejores pr\u00e1cticas de c\u00f3mo desarrollar aplicaciones de criptomonedas Bitcoin usando Node.js y la base de datos NoSQL.","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\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/","og_locale":"es_MX","og_type":"article","og_title":"Developing a Bitcoin Cryptocurrency Application with Node.js and NoSQL","og_description":"Read this Couchbase post to see best practice examples of how to develop Bitcoin Cryptocurrency Applications using Node.js and the NoSQL database.","og_url":"https:\/\/www.couchbase.com\/blog\/es\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/","og_site_name":"The Couchbase Blog","article_author":"https:\/\/www.facebook.com\/thepolyglotdeveloper","article_published_time":"2018-03-28T14:00:44+00:00","article_modified_time":"2025-06-14T01:45:36+00:00","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":"16 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/"},"author":{"name":"Nic Raboy, Developer Advocate, Couchbase","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/bb545ebe83bb2d12f91095811d0a72e1"},"headline":"Developing a Bitcoin Cryptocurrency Application with Node.js and NoSQL","datePublished":"2018-03-28T14:00:44+00:00","dateModified":"2025-06-14T01:45:36+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/"},"wordCount":3718,"commentCount":3,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","keywords":["cryptocurrency","NoSQL Database"],"articleSection":["Best Practices and Tutorials","Couchbase Server","Node.js","SQL++ \/ N1QL Query"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/","url":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/","name":"Bitcoin Cryptocurrency Application with Node.js + NoSQL","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","datePublished":"2018-03-28T14:00:44+00:00","dateModified":"2025-06-14T01:45:36+00:00","description":"Lee este post de Couchbase para ver ejemplos de mejores pr\u00e1cticas de c\u00f3mo desarrollar aplicaciones de criptomonedas Bitcoin usando Node.js y la base de datos NoSQL.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/developing-bitcoin-cryptocurrency-application-nodejs-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\/developing-bitcoin-cryptocurrency-application-nodejs-nosql\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Developing a Bitcoin Cryptocurrency Application with Node.js and 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","author_category":"","last_name":"Raboy","first_name":"Nic","job_title":"","user_url":"https:\/\/www.thepolyglotdeveloper.com","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\/4892","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=4892"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts\/4892\/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=4892"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/categories?post=4892"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/tags?post=4892"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/ppma_author?post=4892"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}