Cómo crear un API usando el MEAN Stack


Ayer hablaba un poco sobre AngularJS con un amigo ayudándole a resolver sus dudas. La plática se tornó de pronto hacia el ámbito de los servicios REST y entonces me dije: ¿Porqué no hacer un tutorial sobre diseño de API's? Cool.

Para empezar, tenemos que entender Qué es un API. Según Wikipedia:

An application programming interface (API) specifies how some software components should interact with each other.

Si hablamos del ámbito web, un API es la interfaz por medio de la cual un usuario puede interactuar con nuestra aplicación/servicio de manera programática, sin necesidad de accesar directamente a la interfaz común del mism@. Generalmente las API's lo que nos devuelven son datos que se pueden utilizar para crear otras aplicaciones que son básicamente extensiones de la aplicación madre. Un ejemplo está en el caso de Twitter, que con su API nos permite acceder a todos los datos de nuestro perfil, tweets, creación de nuevos estados etc, todo esto sin entrar directamente a las apps/páginas originales del servicio. Estos permisos son precisamente los que propician la existencia de un ecosistema rico en clientes nativos para esta red social según la plataforma (por tan sólo citar un ejemplo).

Tipos de API's

A mi en lo personal me gusta dividir las API's en 4 clases:

Pública

Todo mundo puede usarla y acceder a sus datos.

Privada

Solo 1 usuario puede usarla y acceder a sus datos.

Protegida

Solo un grupo reducido de usuarios puede usarla y acceder a sus datos (como los usuarios registrados de alguna app por ejemplo).

Restringida

Al igual que la protegida, sólo un grupo reducido puede usarla y/o tener acceso a sus datos (pero de manera recortada); El acceso total se permite únicamente a un grupo específico (como a los administradores de la aplicación).

Porqué querrías crear un API

Principalmente 2 razones: Acabas de crear un gran servicio de software que quieres que otros usuarios accedan de manera programática o bien, quieres tener acceso de manera programática a data que un sitio web/servicio de software no puede proporcionarte para estos fines de una manera cómoda. Aquí es importante captar si el sitio/servicio/software realmente no tiene un API o un feed que puedas usar en tu caso específico y en todo caso si quiere o no compartir la data que planeas jalar contigo (es diferente poder a querer), ya que lo que haremos aquí para obtener la data en el ejemplo que manejaremos será screen scrapping y en algunos casos esto se podría considerar una práctica ilegal; (Depende de las razones por las que el tercero no tenga un API para la información que quieres el hacer tu propia API para esa data puede ser legal o ilegal), en el caso de nuestro ejemplo, no estamos incurriendo en nada fuera de la ley, ya que la info que tomaremos de todas maneras es pública y está abiertamente disponible para todos por otro tipo de medios aunque no de manera programática como tal, ya que no hay un motivo real para dar ese tipo de acceso para esta info (fuera de la pura enseñanza didáctica como en este caso).

Creando nuestra primera API

Paso 1: Definir utilidad y modelos

En este caso, voy a crear un API para las estadísticas del pool de LTC donde mino, Hypernova; (Más info de qué carambas estoy hablando por acá). El modelo sería para el ente Pools y es muy sencillo:


  • _id
  • name
  • url
  • round
  • pool_hash_rate
  • round_alive_time
  • active_miners
  • active_workers
  • accepted_shares
  • rejected_shares
  • pps_rate
  • network difficulty
  • pool_luck_probability

NOTA: Todos los campos serían del tipo texto, es decir, Strings.

Sin contar los primeros 3 campos de nuestro modelo (que proveríamos nosotros) los demás campos han de ser volátiles, puesto que van a cambiar en vivo con cada request hecha a la API (esto quiere decir que no los guardaremos realmente en nuestra base de datos, sino que los generaremos "al vuelo"). Nótese que estoy haciendo un override del campo _id para asociarlo con el nombre original del pool en lugar de con un string de objeto BSON como hace MongoDB normalmente, esto facilita el acceso a las URL's del API.

Una pausa: Entorno de trabajo

Para este tutorial usaré la mean-boilerplate con un setup idéntico al que vimos en nuestro tutorial de Workflow NodeJS, a partir de aquí asumiré que el usuario tiene este tipo de setup en su máquina para seguir los ejemplos que daré a continuación.

Paso 2: ¡A programar se ha dicho!

Nuestra API sólo va a requerir 1 modelo y 1 controlador, no habrá vistas oficialmente. Los datos los obtendremos directamente del sitio web del pool por medio de una técnica conocida como screen scrapping.

Primero a nuestro esqueleto (dentro de la carpeta "entities") le cambiaremos el nombre a su carpeta "dummies" por "pools", lo mismo en el caso de la carpeta "views" y su subcarpeta "dummies". Dentro de la recién renombrada "views/pools" borraremos TODAS las vistas, excepto la de admin.jade.

El modelo

Un modelo básico de Mongoose con las características detalladas anteriormente (Nótese que los atributos volátiles no están declarados):


El controlador

Un controlador RESTful básico compatible con respuestas e interacciones JSON, nótese que nuestra API es una mezcla entre un API pública y una privada, ya que las acciones básicas para lectura (Index y Show para mostrar todos los pools y uno específico al usuario respectivamente) son accesibles para cualquiera pero las acciones especiales para escritura y administración (Create/Destroy para crear un nuevo pool en la DB y borrar alguno de los ya existentes respectivamente) están protegidas con la implementación de Salted Digest Auth propia de la mean-boilerplate para evitar problemas de seguridad; Nótese también que tenemos un espacio para Funciones Extra donde declaro una función que voy a usar para complementar mi scrapping y que las acciones Index y Show tienen implementado el código para el mismo dentro de ellas, de esta manera obtenemos los datos no persistentes al vuelo:


Generalidades

app.js

Aquí simplemente eliminé el loading de los modelos y la variable auth, al no ser necesarios:


package.json

Nótese que le cambié el nombre a mi app, dejé una versión acorde e instalé el módulo scrap para las cuestiones del screen scrapping:


routes.coffee

Cambié el archivo de rutas un poco de manera acorde:


layout.jade

Acá cambié las cuestiones generales de información de la layout y añadí un "noindex" para que la app no apareciera en buscadores si la llego a subir (pues solo es una API y no tiene nada que mostrar), también le di el nombre del módulo AngularJS a la etiqueta HTML:



Finalmente: Probando el API

Hemos terminado, nuestra API fue creada de manera exitosa. ¿Cómo la "testeamos"? sencillo, corremos un npm install seguido de un npm start dentro de la carpeta de nuestro proyecto en consola y después en otra pestaña (o ventana) de terminal podemos echar mano del comando curl en consola para verificar el funcionamiento del API recién creada:



Aquí recuerden primero correr el comando de New/Create para crear su pool y cambiar :id por hypernova en los comandos donde aplique para los tests... También recuerden que pueden cambiar los datos de acceso para el usuario administrativo en el archivo app.yaml de la carpeta "config" de su aplicación.

¿Y AngularJS?

AngularJS no tiene un papel importante para jugar en la creación de un API de este tipo a decir verdad, pero para no salirnos del ámbito MEAN, lo podemos usar para hacer tests de alto nivel en nuestro navegador. Un ejemplo (teniendo nuestra base de datos populada con el pool de ejemplo):

public/coffee/custom.coffee (recuerden compilarlo a su contraparte JS)


views/pools/admin.jade


public/css/overrides.less (este se compila solo)


Extras: API Versionada

Cuando estamos hablando de un API para producción y uso real, la praxis ideal es tener un API con rutas versionadas. Pueden obtener más info sobre cómo implementar dichas rutas en este enlace.

Con esto terminamos nuestro tutorial de hoy... Hay mucho más que podemos hacer para mejorar lo aquí implementado, como por ejemplo refactorizar el código del scraping en el controlador para hacerlo más DRY o hacer más con AngularJS aparte de simples tests de alto nivel, eso ya queda en sus manos.