API Node.js para AUVASA

por Ricardo Vega el 27/05/2019

Hoy te quería hablar de un pequeño "Side Project" que he realizado el último fin de semana.

La intrahistoria

Actualmente, estoy viviendo en Valladolid y por motivos laborales, tengo que coger varios días a la semana el autobús. El servicio de autobuses municipal está operado por AUVASA que pone a disposición del público una aplicación móvil y una aplicación web desde la que es posible revisar los tiempos de llegada esperados para los diferentes buses en una determinada línea.

El "problema" es que AUVASA no dispone de una API (al menos pública) para la consulta de esta información. La aplicación web se renderiza en el servidor por lo que devuelve directamente HTML.

Revisando por internet me encontré con un proyecto en Github de @luisddm llamado Auvasa Scrapper que exponía una API REST a través del parseo del HTML de la web. Pero esto no es lo único que hace este proyecto, también expone una serie de vistas con esta información. Esta última parte para mi era irrelevante pero, por contra, echaba en falta la posibilidad de filtrar los datos en la API (por ejemplo por linea de bus).

Así que decidí que yo también podía hacer mi propia API no oficial, sin las capacidades de renderizar la información de una forma diferente; exclusivamente exponiendo una API REST para consultar el estado de una parada pero permitiendo filtrar la información, por ejemplo por número de linea.

Nació auvasa-api.

Bus AUVASA

La arquitectura

En mi día a día, trabajo con Javascript (y Typescript) por lo que es un lenguaje que conozco y con el que me siento cómodo. Sin embargo, este trabajo se suele reducir a frameworks SPA como React, Angular o Vue y, aunque he trabajado algo con Node.js, no es para nada una tecnología con la que conviva todos los días.

Lo que quería hacer lo tenía claro, montar una API REST con filtros que internamente "[scrapeara](https://es.wikipedia.org/wiki/Web_scraping" el HTML de una web. Aunque valoré otros lenguajes de programación, decidí optar por Node.js por dos motivos:

  • la experiencia de montar un side project con Node (antes siempre había usado Python para el lado del servidor).
  • en Python ya había parseado webs, pero en Node no.

También tenía claro que aunque fuese a ser un proyecto personal de fin de semana, no iba a ser un batiburrillo. Estaba claro que iba a tener un controlador que procesase la llamada REST (con sus parámetros de filtrado) pero lo que es el parseo, lo iba a delegar en un servicio para intentar seguir el principio de responsabilidad única.

Aunque mi idea era testear la aplicación, es verdad que no hay mucho a testear más allá de la integración (cuya información es completamente dinámica y dependiente del día y hora) por lo que no se me ocurrió una forma de escribir un test repetible independientemente de cuando sea ejecutado sin mockear la web de AUVASA que era precisamente lo que quería testear.

Todo lo que sea hacer tests de una aplicación, genial, pero con el objetivo de tener confianza en lo que haces y servir de alerta antes fallos. Testear por testear, me parece una pérdida de tiempo.

Con estos preceptos sobre la mesa y tras una serie de investigaciones, decidí emplear las siguientes librerías:

  • cheerio: parseo de HTML en servidor empleando una API similar a la de JQuery.
  • request-promise: librería para llevar a cabo peticiones HTTP de forma simplificada y con soporte para promesas.
  • express: framework web para Node.js con el que construimos la API REST. Ya lo conocía.

Si revisas el código, podrás apreciar que está ordenado de una forma un tanto extraña, esto se debe al siguiente punto...

Despliegue

Este proyecto no deja de ser una pequeña prueba de concepto que no pretende dar servicio a miles de usuarios. Tampoco voy a ganar un duro con él así que la idea es también que no me cueste nada.

Mi idea desde el principio ha sido usar Firebase Functions ya que es un servicio que conozco y es muy flexible. Para cargas pequeñas como pruebas de concepto suele ser suficiente con la capa gratuita por lo que todo eran ventajas.

La estructura que tiene el proyecto si ves el código fuente se ajusta a la estructura típica de las Firebase Functions, con la lógica dentro de la carpeta functions. Es más, si descargas/clonas el proyecto, podrás desplegarlo directamente en Firebase Functions si creas un proyecto llamado "auvasa-api".

El pequeño problema del que no fui consciente hasta que no desplegué, es que la capa gratuita de Firebase Functions sólo permite hacer llamadas HTTP a servicios de Google y yo tenía que consultar la web de AUVASA, resultando en un bloqueo.

Por ello, y visto que desde el primer momento había querido mantener el enfoque API Express.js, me fue fácil encontrar una alternativa y recurrir a otro servicio que conocía bien, Heroku.

Integración Continua

Si revisas el fichero .gitlab-ci.yml podrás ver como hago el despliegue continuo en Heroku, para lo cual tuve que crear previamente una aplicación en Heroku llamada auvasa-api y copiar el API_KEY que proveé Heroku como variable dentro de la configuración de CI/CD de Gitlab.

Esta estrategia la puedes aplicar en cualquier proyecto Node.js prácticamente copiando el fichero .gitlab-ci.yml y cambiando en la línea 14 el parámetro --app con el nombre de tu aplicación en Heroku.

Yo un fallo que cometí fué en el fichero index.js que se encuentra en la raíz del proyecto y que sirve de arranque de la aplicación, fijé la constante PORT a 5555, sin tener en cuenta ni dar prioridad a la variable de entorno como está ahora,por lo que en Heroku no se desplegaba en el puerto correcto.

Otro detalle a reseñar es que la aplicación se arranca ejecutando el comando npm run start por lo que debemos asegurarnos que dentro del package.json exista un script start que realmente arranque la aplicación.

"Producción" y uso

La aplicación, aunque como digo no está pensada para su uso masivo, se encuentra disponible en https://auvasa-api.herokuapp.com/

Puedes acceder a la API mediante https://auvasa-api.herokuapp.com/stop/813. En este ejemplo, buscaremos la información en tiempo real para la parada nº 813 que corresponde con Plaza España Bola del Mundo.

Como ejemplo adiciones:

Entiendo que si no eres de Valladolid, no tiene mucho sentido su uso pero siéntete libre de emplearla si quieres y abrirme incidencias en auvasa-api.

Además, puedes "forkear" el proyecto y adaptarlo a tu ciudad simplemente modificando el parseador del fichero functions/service.js

Nota adicional

Si vives en Valladolid, mi compañero de trabajo Sanjo, ha creado un atajo para la aplicación "Atajos" de iOS que hace uso concreto de esta API para informar al usuario si "llega" o no a su próximo bus.

Le he pedido permiso para compartir contigo el atajo y que puedas ver cómo está implementado. Gracias @Sanjo!

Un saludo y nos vemos en breve.

Disclaimer

La API por supuesto no es oficial ni cuenta con el permiso expreso de AUVASA. Igualmente, no existe un fin comercial sino divulgativo en este proyecto (tanto en el código fuente públicamente expuesto como en el servicio web desplegado).

Apoya al blog


Si te ha gustado este artículo, valora apoyarme económicamente a través de Patreon, una plataforma de Micro-mecenazgo con la que puedes hacerme un donativo que ayude a la continuidad del blog. Una pequeña ayuda significa mucho. 😃

Permanezcamos en contacto!


¿Quieres enterarte de todas las novedades del sector? ¿Te gustaría trabajar conmigo? ¡Puedes contactar conmigo de forma muy sencilla!