No hace mucho escribí sobre crear un acortador de URL con Node.js y Couchbase N1QL. Si aún no lo has visto, el artículo es una gran introducción a Couchbase y a la creación de API RESTful. usando Node.js y Express Framework. La trampa detrás de ese artículo, y no es una mala trampa, es que usted tiene que preferir escribir consultas SQL para gestionar sus datos NoSQL.
¿Y si prefiere utilizar un Modelo de Documento Objeto (MDO) en lugar de escribir consultas?
Esta vez vamos a explorar el proceso de desarrollo detrás de la creación de un acortador de URL que utiliza Ottoman ODM, Couchbase Server y Node.js.
Requisitos
No hay muchos requisitos cuando se trata del éxito de esta aplicación. Por ejemplo, lo siguiente es lo que necesitarás instalado y configurado:
- Servidor Couchbase 4.1+
- Node.js 4.0+
Dado que se trata de una aplicación Node.js, necesitaremos tenerlo instalado y usaremos Node Package Manager (NPM) para reunir todas las dependencias de nuestro proyecto. Para sacar el máximo provecho de Ottoman vamos a querer utilizar Couchbase 4.1 o superior.
Comprender el modelo de datos y preparar la base de datos Couchbase
Para este ejemplo vamos a crear un cubo llamado ejemplo en Couchbase Server. No es necesario llamarlo así, pero a lo largo del artículo nos referiremos a él como tal. No se requieren índices o configuraciones adicionales en nuestro cluster más allá de la creación del bucket.
En cuanto a los datos, el plan es almacenar un documento por cada URL que queramos reducir. Cada documento podría ser algo como esto:
1 2 3 4 5 6 |
{ "_tipo": "Url", "hash": "2xmx9qpyj", "longUrl": "https://www.thepolyglotdeveloper.com/2016/05/using-couchbase-nosql-nativescript-angular-2-mobile-app/", "shortUrl": "http://localhost:3000/2xmx9qpyj" } |
En Tipo
es algo definido por el modelo Ottoman, pero las otras tres son propiedades que vamos a definir. El hash va a representar un hash único de un valor. Este hash va a actuar como el identificador en cada una de nuestras URLs cortas.
Desarrollo de la aplicación Node.js URL Shortener
Con la base de datos lista y una comprensión básica de nuestro modelo de datos, podemos trabajar en la creación de nuestra aplicación de acortador de URL Node.js. La aplicación dependerá en gran medida de Express Framework y una biblioteca llamada Hachís. Vamos a utilizar Hashids para ahorrarnos la molestia de tener que escribir nuestro propio algoritmo de hash corto.
Creación del proyecto con las dependencias
Empecemos creando un nuevo proyecto Node.js e instalando todas las dependencias necesarias del proyecto. Desde el Terminal (Mac y Linux) o Símbolo del sistema (Windows), ejecute lo siguiente:
1 |
npm init --y |
El comando anterior creará un nuevo paquete.json para nuestro proyecto. Este archivo contendrá toda la información de dependencia, así como otras cosas.
Ahora tenemos que instalar cada una de las dependencias necesarias en nuestro proyecto:
1 |
npm instale couchbase express otomana cuerpo-analizador hashids --guardar |
El comando anterior instalará el SDK de Couchbase, Express Framework, Ottoman, Hashids, y la librería Body Parser para aceptar datos del cuerpo en peticiones HTTP.
En este punto podemos empezar a arrancar la aplicación para prepararla para el trabajo pesado.
Puesta en marcha de la aplicación
Para simplificar las cosas, todo el código de nuestra aplicación residirá en un único archivo dentro de nuestro proyecto llamado index.js. En un entorno de producción es probable que desee dividirlo para facilitar el mantenimiento, pero para este ejemplo muy simple que va a estar bien.
Abra el index.js e incluir importaciones a cada una de las dependencias que instalamos:
1 2 3 4 5 |
var Couchbase = requiere("couchbase"); var Otomano = requiere("otomana"); var Express = requiere("express"); var BodyParser = requiere("body-parser"); var Hachís = requiere("hashids"); |
Con las dependencias importadas, ahora podemos inicializarlas para su uso dentro de la aplicación:
1 2 3 4 |
var aplicación = Express(); aplicación.utilice(BodyParser.json()); aplicación.utilice(BodyParser.urlencoded({ ampliado: verdadero })); |
En lo anterior estamos diciendo esencialmente que queremos inicializar Express Framework y aceptar JSON y datos del cuerpo codificados en URL de las solicitudes.
En este punto podemos establecer una conexión con nuestro cluster Couchbase y abrir el bucket de la aplicación:
1 |
Otomano.cubo = (nuevo Couchbase.Grupo("couchbase://localhost")).openBucket("ejemplo"); |
Para utilizar Ottoman es necesario que existan ciertos índices. Por suerte podemos hacer que Ottoman cree estos índices por nosotros si aún no existen. Cuando estemos seguros de que existen, podemos empezar a servir la aplicación.
1 2 3 4 5 6 7 8 |
Otomano.ensureIndices(función(error) { si(error) { consola.registro(error); } var servidor = aplicación.escuche(3000, función() { consola.registro("Escuchando en el puerto %s...", servidor.dirección().puerto); }); }); |
En nuestro ejemplo la aplicación servirá localmente en el puerto 3000.
Hasta ahora sólo tenemos el framework base funcionando para nuestra aplicación de acortador de URL. En realidad no tenemos ninguna lógica de acortamiento o interacción con la base de datos en lo que respecta a operaciones CRUD o de consulta.
Creación del modelo de datos
Sabemos cómo se verán los datos en la base de datos, pero aún no sabemos cómo crearlos. Dado que estamos utilizando un ODM en lugar de consultas N1QL tenemos que definir el modelo dentro de nuestro código.
En este proyecto concreto, el modelo es muy simplista. Se vería así:
1 2 3 4 5 6 7 |
var UrlModel = Otomano.modelo("Url", { hash: {tipo: "cadena", sólo lectura: verdadero}, longUrl: "cadena", shortUrl: "cadena" }, { id: "hash" }); |
En el modelo de datos anterior, que llamamos Url
tenemos tres propiedades. El hash
representará el id de nuestro documento. No es un requisito establecer manualmente el id del documento, pero para este proyecto tiene sentido. Establecer manualmente un id de documento requiere que la propiedad sea de sólo lectura. Las otras dos propiedades son valores de cadena estándar.
El modelo puede ser infinitamente más complejo en función de sus necesidades.
Aplicación del modelo de datos en una serie de métodos RESTful Endpoint
La aplicación que estamos construyendo es un servicio web RESTful por lo que necesitamos crear algunas funciones endpoint. La aplicación contendrá los siguientes tres endpoints públicos:
1 2 3 |
/ /crear /ampliar |
Cuando el raíz la aplicación nos llevará a la URL larga almacenada en Couchbase. Cuando el /crear intentaremos encontrar una URL corta previamente guardada o crear una si no existe. Hacemos esto porque no queremos guardar múltiples copias de una URL larga. Finalmente tenemos un /ampliar para que podamos ver a dónde va nuestra URL corta.
Empezando por el /crear tenemos el siguiente método, que es probablemente la parte más compleja de nuestra aplicación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
aplicación.Correo electrónico:("/crear", función(consulte, res) { si(!consulte.cuerpo.longUrl) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una URL larga"}); } UrlModel.encontrar({"longUrl": consulte.cuerpo.longUrl}, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } si(url.longitud == 0) { var hashids = nuevo Hachís(); var urlObj = nuevo UrlModel({ hash: hashids.codificar((nuevo Fecha).getTime()), longUrl: consulte.cuerpo.longUrl }); urlObj.shortUrl = "http://localhost:3000/" + urlObj.hash; urlObj.guardar(función(error, resultado) { si(error) { devolver res.estado(400).enviar(error); } res.enviar(urlObj); }); } si no { res.enviar(url[0]); } }); }); |
El punto final anterior es un punto final de solicitud POST que espera un cuerpo. Si no se encuentra una URL larga en el cuerpo, se devolverá un error. En caso contrario, se realizará una búsqueda otomana basada en la URL larga proporcionada. Si se encuentran datos en la búsqueda, esos datos se devolverán al usuario, de lo contrario se iniciará un proceso para crear una nueva entrada.
Durante la creación de una nueva entrada, se crea un hash único basado en la marca de tiempo. Este hash se añade a un nombre de host corto y se guarda en la base de datos junto con el propio hash y la URL larga. El objeto se devuelve cuando se ha guardado correctamente.
Cuando se trata de la /ampliar tenemos lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 |
aplicación.consiga("/expand", función(consulte, res) { si(!consulte.consulta.shortUrl) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una URL corta"}); } UrlModel.encontrar({"shortUrl": consulte.consulta.shortUrl}, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } res.enviar(url.longitud > 0 ? url[0] : {}); }); }); |
El método es similar al que vimos en el /crear pero no es exactamente lo mismo. En su lugar, hacemos una búsqueda basada en la URL corta proporcionada. Si se encuentra, devuelve el documento que contiene la URL larga; en caso contrario, devuelve un objeto vacío.
Por último, tenemos el raíz endpoint. Este endpoint es responsable de la redirección a la URL larga y requiere un id que en realidad es nuestro hash. Recuerda, estamos alojando la API y las URLs cortas en el mismo servidor.
1 2 3 4 5 6 7 8 9 10 11 |
aplicación.consiga("/:id", función(consulte, res) { si(!consulte.parámetros.id) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una identificación"}); } UrlModel.getById(consulte.parámetros.id, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } res.redirigir(url.longUrl); }); }); |
Digamos que navegamos a http://localhost:3000/2xmx9qpyj en el navegador. El sitio 2xmx9qpyj es nuestro id, que es también nuestro valor hash. Podemos hacer una búsqueda de documentos basada en este id y navegar si existe.
No está mal, ¿verdad?
El código fuente completo de la aplicación
En caso de que desee ver toda la aplicación reunida, puede encontrarla a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
var Couchbase = requiere("couchbase"); var Otomano = requiere("otomana"); var Express = requiere("express"); var BodyParser = requiere("body-parser"); var Hachís = requiere("hashids"); var aplicación = Express(); aplicación.utilice(BodyParser.json()); aplicación.utilice(BodyParser.urlencoded({ ampliado: verdadero })); Otomano.cubo = (nuevo Couchbase.Grupo("couchbase://localhost")).openBucket("ejemplo"); var UrlModel = Otomano.modelo("Url", { hash: {tipo: "cadena", sólo lectura: verdadero}, longUrl: "cadena", shortUrl: "cadena" }, { id: "hash" }); aplicación.consiga("/expand", función(consulte, res) { si(!consulte.consulta.shortUrl) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una URL corta"}); } UrlModel.encontrar({"shortUrl": consulte.consulta.shortUrl}, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } res.enviar(url.longitud > 0 ? url[0] : {}); }); }); aplicación.Correo electrónico:("/crear", función(consulte, res) { si(!consulte.cuerpo.longUrl) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una URL larga"}); } UrlModel.encontrar({"longUrl": consulte.cuerpo.longUrl}, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } si(url.longitud == 0) { var hashids = nuevo Hachís(); var urlObj = nuevo UrlModel({ hash: hashids.codificar((nuevo Fecha).getTime()), longUrl: consulte.cuerpo.longUrl }); urlObj.shortUrl = "http://localhost:3000/" + urlObj.hash; urlObj.guardar(función(error, resultado) { si(error) { devolver res.estado(400).enviar(error); } res.enviar(urlObj); }); } si no { res.enviar(url[0]); } }); }); aplicación.consiga("/:id", función(consulte, res) { si(!consulte.parámetros.id) { devolver res.estado(400).enviar({"status": "error", "mensaje": "Se requiere una identificación"}); } UrlModel.getById(consulte.parámetros.id, función(error, url) { si(error) { devolver res.estado(400).enviar(error); } res.redirigir(url.longUrl); }); }); Otomano.ensureIndices(función(error) { si(error) { consola.registro(error); } var servidor = aplicación.escuche(3000, función() { consola.registro("Escuchando en el puerto %s...", servidor.dirección().puerto); }); }); |
Si ejecuta el código anterior con npm index.js
puede empezar a utilizar cada uno de los puntos finales de la API que se encuentran en http://localhost:3000.
Conclusión
Acabas de ver cómo crear una aplicación acortadora de URLs usando un Modelo de Documento Objeto (ODM) llamado Ottoman, Couchbase Server y Node.js. Si no eres un fan de los ODMs o quieres ver cómo hacer esto con consultas SQL, puedes echar un vistazo a un artículo de tutorial anterior Escribí sobre el tema.
¿Quieres llevar este tutorial un paso más allá? ¿Por qué no intentar recopilar información analítica de las personas que intentan utilizar las URL cortas? Podrías mantener un contador de visitas dentro de los documentos antes de la navegación o incluso almacenar información como los agentes de usuario del navegador.