Como crear una aplicación web usando el MEAN stack

Hoy os explicaré como crear una aplicación usando el MEAN stack, todo en puro Javascript tanto en el servidor como en el lado cliente. Las iniciales de MEAN se refieren a Mongo Express Node Angular.

Ten en cuenta de que como que Node y Mongo son relativamente nuevos (menos de 10 años) a la hora de poner la aplicación en producción no se encuentran tantas opciones/empresas/hosting que cuando usas el LAMP stack (Linux Apache Mysql PHP)

MEAN vs LAMP

Si buscas el termino MEAN en Google mira de poner stack al final ya que esa palabra tiene muchos significados en inglés 😜

A día de hoy se puede usar Javascript en el servidor desde que salió NodeJS por allá el 2007 por lo que uno ya puede crear una aplicación entera solo usando Javascript.

Mongo vendría a ser la base de datos, es una NoSQL que se diferencia de las típicas DB en que su estructura en vez de ser tablas son documentos. No entraremos en detalle primero porque no soy un maestro en ello y segundo porque habría mucho que explicar :)

https://www.mongodb.com/nosql-explained

https://es.wikipedia.org/wiki/NoSQL

Express sería el NodeJS framework que se usa en el lado del servidor, será para conectar con la DB, crear el servidor web, la API y para muchísimas cosas más.

Y para terminar tenemos Angular, que es un framework creado por Google para crear SPAs (Single Page Application) en el navegador sin la necesidad de usar un servidor.

Porque MEAN es una apuesta segura? Supongo que la respuesta dependerá bastante sobre tus preferencias pero algo a tener en cuenta es que Javascript está dominando el mundo web, hará unos años su territorio era exclusivo en el navegador pero desde que apareció node ha ido tomando terreno a otros lenguajes que se usaban para crear el backend ya sea PHP, Ruby, Java...

A mi node me salvo la vida, empecé a programar aprendiendo javascript y ya al poco tiempo me di cuenta de que uno ha de tener un poco de conocimientos sobre como funciona el backend. Ver que tenia que aprender otro lenguaje me abrumó un poco, solo hasta que puse en Google "create a web server in Javascript" => booom => node. Ese día ya vi que javascript era el lado oscuro que tenía que seguir, de eso hará ya unos 4 años y por ahora no me arrepiento. Durante este tiempo han surgido miles de tecnologías basadas o que se pueden utilizar con node para así crear un ecosistema puro de Javascript como por ejemplo MongoDB.

Cualquier developer que se dedique a web y use PHP, ruby o le que sea tarde o temprano va a tener que lidiar con el frontend aka Javascript. La mayoría de backend developers toman a JS como un juguete al cual maldecir todo el rato cuando algo no sale bien pero desde mi humilde experiencia es más bien su inexperiencia con JS la que les lleva a esa situación. En mi curro (todos son full stack developers en C++), no me toman en serio (se mofan de mi) cuando digo que lo quiero hacer todo en node pero yo creo que se equivocan, o no?

Java, write once compile anywhere

C++, write once compile anywhere

Javascript, just write it

Es por eso que creo que MEAN es una buena stack, todo es en Javascript y abarca tanto el backend como el frontend por lo que en un solo lenguaje vas a ser capaz de crear un appp de los pies a la cabeza.

A día de hoy ya grandes compañías como Paypal, Netflix, Linkedin usan node:

  1. https://www.quora.com/What-companies-are-using-Node-js-in-production
  2. https://blog.risingstack.com/node-js-examples-what-companies-use-node-for/
  3. https://www.netguru.co/blog/top-companies-used-nodejs-production

Se nota que me gusta Javascript?

Como que la mejor manera de aprender es creando algo que tenga un poco de pies y cabeza, vamos a haver una aplicación donde podrás buscar series y películas en IMDB y así poder crearte un listado de tus títulos favoritos donde les podremos hacer un rating, actualizar y eliminar.

Instalación

Vamos primero de todo a instalar todo lo que necesitamos para poder empezar. Todos los pasos asumen que vas a usar Mac, para windows deberías de hacer un poco de búsqueda por tu cuenta 😞

Con la instalación de Nodejs a su vez se instalará NPM que vendría a ser el Node Package Manager, a día de hoy la librería más grande de librerías 😕 a disposición de un developer.

Yo siempre uso el instalador que ofrece la página oficial de NodeJS, selecciona la versión que diga Recomendado para la mayoría, los pasos a seguir son los mismos como si instalases una aplicación cualquiera. Hay otras maneras de instalar Node pero siempre he encontrado que la lían un poco con tu $PATH y donde acaban poniendo los ejecutables y carpetas lib. Sabrás que todo se instaló a la perfección si abres una ventana del terminal.app y al escribir node y presionar enter ves este signo >.

Lo que estas viendo es el REPL de Node (Read-Eval-Print Loop), en cristiano vendría a ser una ventana donde puedes interactuar con el ordenador escribiendo Javascript. Si escribes 3+4 y presionas enter verás un 7. Para salir de este modo debes de presionar ctrl+c un par de veces.

Para tener instalado Mongo en tu Mac la mejor manera de hacerlo es a través de Homebrew, que vendría a ser un instalador de paquetes para mac. Su lema es The missing package manager for macOS. Te preguntarás, no podría instalar Node con homebrew? pues si pero en mi opinion la lía un poco con el $PATH. Para instalar homebrew debes de hacerlo a través del terminal usando este comando /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Una vez acabe vas a poder instalar Mongo usando este comando brew install mongodb. Cuando la instalación haya terminado debes de hacer un par de cosas antes de poder iniciar la DB.

  1. Crear la carpeta donde tus datos/colecciones van a guardarse usando este comando mkdir -p ~/data/db
  2. Asegúrate de que lo creas en el directorio que quieras
  3. Otra cosa a tener en cuenta es que tengas lo permisos correctos para escribir en ese directorio recién creado
  4. sudo chown -R id -un /data/db
  5. Escribe tu contraseña

Si todo va como lo esperado ya puedes iniciar el servidor/daemon Mongo usando el comando mongod. Para usar la Mongo Shell (interactuar con la DB) debes de abrir otra ventana de tu terminal y usar el comando mongo, verás el típico >

Para salir de la shell puedes usar los comandos exit o quit() y para parar el servidor teclea ctrl+c.

Quien dijo que web development era fácil, seguro que en el momento de iniciar mongo por primera vez vas a tener algunos errores pero para eso esta stackoverflow o este tutorial.

Instalar Mongo usando un instalador

Bueno, toda esta parrafada para solo tener la M y N del MEAN stack jejejeje En sí tanto Node como Mongo los hemos instalado de manera global y no son dependencias directas de la aplicación/web que quieras escribir.

A partir de ahora todo lo vamos a hacer usando NPM.

 Empieza el coding

Porque no empezar creando una repo en Github? https://github.com/byverdu/meanStackPost

En tu local environment creamos un directorio

> mkdir myAwesomeApp
> cd myAwesomeApp
> npm init

npm init crea el archivo package.json que vendría a ser el punto de entrada de toda aplicación que use NodeJS. Este comando te guiará en rellenar las entradas más comunes que hay en dicho archivo.

Ahora deberíamos empezar creando la estructura de carpetas que usará nuestra aplicación.

app/
   |-- bin/
   |    |- www
   |-- client/
   |     |- js/
   |     |- static/
   |     |- views/
   |
   |-- server/
   |      |- API/
   |      |- models/
   |      |- routes/
   |      |- db.js
   |      |- index.js
build/
   |-- tasks/
   |    |- gulpTasks.js
   |
tests/
   |-- server/
   |-- client/
   |
   |
utils/
   |
   |
gulpfile.babel.js
package.json

Para hacer las cosas bien hechas y tener confianza plena en que nuestro código funciona como un reloj vamos a escribir algunos tests para comprobar que lo que estamos haciendo es correcto y así evitarnos sorpresas. Empezaremos por testear algunas funciones que necesitaremos para buscar en Imdb, el Schema que vamos a usar en Mongo, continuaremos con las rutas del router y acabaremos con los tests para Angular.

También deberíamos instalar un par de paquetes NPM de manera global tales como mocha y nodemon, el primero sería un framework que nos proporcionará una sintaxis para escribir tests y el segundo vendría a ser una utilidad que va a monitorear tus archivos y en cuanto detecte un cambio hará que tu servidor se reinicie.

npm install -g mocha nodemon

Ahora viene la parte donde tenemos que escribir la lista sin fin de paquetes NPM, tendremos que diferenciar los que son dependencias directas de la aplicación (vamos, los que sin ellos no va a funcionar nada) y los que son una dependencia solo para mientras hacemos la aplicación. Puede que los dos más importantes sean express y mongoose, este último sería una manera más fácil de crear Schemas e interactuar con Mongo.

npm install --save express mongoose mongodb-uri

Como que estaremos usando la última version de Javascript (es6 o es2015) tendremos que instalar unas cuantas dependencias para que node pueda entender los keywords que vamos a usar.

npm install --save-dev babel-preset-es2015 babel-register chai supertest

En muchos tutoriales empiezan por enseñarte el código y luego el test, algo que me da rabia por lo que yo empezaré con escribir el test y después el código. Rule of thumb en TDD (Test Driven Development) red green refactor, escribe un test que falle, escribes la solución más sencilla para que pase y después esplayarse.

Back End

Para ejecutar los tests usaremos el terminal, para hacerlo un poco más sencillo en nuestro archivo package.json hay una sección llamada scripts que se puede usar para crear alias de los comandos que suelen ser bastante largos :)

"scripts": {
    "server-test": "nodemon -e js,pug --exec \"mocha --compilers js:babel-core/register\" test/server-test/",
    "client-test": "karma start",
    "nodemon": "nodemon -e js,pug --exec babel-node  ./app/bin/www",
    "start": "babel-node  ./app/bin/www"
  }

Tests para Utils

Este sería el listado de paquetes npm que me he encontrado para buscar películas/series en Imdb:

  • name-to-imdb, este paquete nos ayudará a buscar el id en Imdb por cada película que queramos buscar.

  • imdb, una vez que tengamos el id, podremos buscar directamente en Imdb. La pega de este paquete es que en vez de consultar directamente la API de omdb, lo que hará es hacer un web scrapping a la página de Imdb, lo que supone un poco de lentitud.

Lo que vamos a hacer es usar Promises para poder controlar un poco el resultado asíncrono los paquetes npm que vamos a usar.

Los pasos a seguir usando TDD sería la siguiente,

1- En un archivo escribimos el test,

// utilsSpec.js
describe( 'Util helper methods', () => {
  it( 'Util. resolveImdbCall is defined', () => {
     expect( resolveImdbCall ).not.to.eql( undefined );
   });
});

2- Ejecutamos el test con npm run server-test, el cual fallará porque getImdbId no existe :(

3- Escribimos el código suficiente para que el test pase

// utils.js
const resolveImdbCall = () => 'I am a passing test';

export {
  resolveImdbCall
}

En el gist puedes ver el resto de tests y la implementación de ellos

Archivos en Github

Tests para el Schema

Ahora vamos a por el Schema que nuestra base de datos utilizará, para eso primero tenemos que coger un papel y lápiz y ponernos a pensar que propiedades vamos a querer guardar. En mi caso he pensado que podríamos tener estas: title, poster, rating, myRating, description, imdbUrl, genres y type. Usaremos type para especificar si es película o serie.

Tendremos que configurar una base de datos de muestra para poder interactuar con ella mientras hacemos los tests.

Este gist sería un resumen de los tests para el Schema

Los test continúan ejecutándose usando npm run server-test

Archivos en Github

Tests rutas

Ahora el siguiente pase vendría a ser la creación del servidor web. El cual tendría la siguiente pinta:

Como que estamos haciendo una aplicación en Angular nuestro servidor web básicamente se va a encargar de conectarse con la base de datos y hacer las típicas operaciones que hace una API (Application Programming Interface), Create Read Update Delete, o sea poder crear, leer, actualizar y eliminar documentos en nuestra base de datos.

Para crear el servidor usaremos el paquete npm express, en el cual definiremos siguientes rutas:

  • /api/all/:imdb, donde :imdb será el parámetro que pasaremos al router el cual podrá ser o movie o series.
  • /api/search/, donde utilizaremos nuestras utils para buscar en Imdb.
  • /api/add/, donde añadiremos items a nuestra DB.
  • /api/update/:id, donde actualizaremos items usando su id.
  • /api/delete/:id, donde eliminaremos items usando su id.
  • Home, / donde podremos buscar y salvar películas o series.
  • Películas y Series, compartirán el mismo fragmento de Url pero veremos un resultado u otro dependiendo de los parámetros pasados.
  • /imdb/:collection para ver toda la colección o
  • /imdb/:collection/:id para ver el detalle de un item.

La idea continua siendo la misma Red-Green-Refactor y usando npm run server-test para ejecutar los tests

Archivos en Github

Las rutas de nuestra aplicación serán manejadas entre el servidor y ng-route de Angular.

Nuestro servidor tendría que tener la habilidad de servir páginas html para todas las rutas que nuestra aplicación vaya a utilizar.

Por ejemplo, en ng-route vamos a tener que usar varias templates html, las cuales el servidor tiene que saber sobre ellas. La ruta sería la siguiente:

router.get( '/views/:fileName', ( req, res ) => {
  res.render( req.params.fileName );
});

Otro conjunto de rutas que deberíamos especificar sería para la página de inicio, la de las colecciones y la de cada serie/película.

const allRoutes = [  
  '/',
  '/imdb/:collection',
  '/imdb/:collection/:id'
];

router.get( allRoutes, ( req, res ) => res.render( 'layout' ));  

Podríamos usar router.get( *, ... ) para especificar todas las rutas pero me he encontrado que cuando estaba escribiendo los tests del API me daba problemas al user router.get.

archivo routes.js

Ya está 😂, bueno la parte del servidor... Tan solo nos quedaría:

  • Usar gulp para automatizar todo el proceso de crear el front end y la
  • implementación del front end con Angular y consumir la API.

Front End

Esta sería la estructura de nuestro frontend:

client/
   |-- js/
   |     |- controllers/
   |     |- services/
   |     |- router.js
   |
   |-- static/
   |     |- bundle.js/
   |       
   |-- views/

A Angular le podríamos encontrar una pega y es que a medida que nuestra aplicación crece, el número de archivos que tenemos que incluir en el html acaba siendo bastante alto, claro que podemos minificar y buscar otras alternativas para disminuir la cantidad de http requests que hacemos pero vamos a aprovecharnos de que estamos usando node y el entorno npm para utilizar browserify.

Browserify lets you require('modules') in the browser by bundling up all of your dependencies.

Con browserify vamos a poder escribir nuestro front end como si fuese una aplicación de node para después añadirlo todo en un solo archivo el cual usaremos en nuestro html.

Otra cosa que vamos a hacer es usar una sintaxis diferente a la hora de escribir el código en Angular, se llama el controller as syntax. La idea es dejar de usar $scope en nuestros controllers y en el html forzar el uso de ng-controller="HomeController as home" para así ser un poco más declarativo con el scope de cada controller.

Explicación más en detalle para controller as.

Otra cosita antes de empezar :(, hablo mucho de html pero en si vamos a escribir muy poco. Vamos a usar un pre-procesador de html (what the heck Albert!), vendría a ser una manera más corta de escribir html sin la necesidad usar los tediosos <> ni tener que cerrar las etiquetas html. Usaremos Pug como pre-procesador de html.

Para que te hagas una idea en vez de escribir <a ng-href="{{property}}">What the hell </a> escribes a(ng-href="{{property}}) What the hell.

Creo que me voy a saltar los tests para angular y toda la configuración de gulp, más que nada porque el post se está extendiendo demasiado.... Aún así pondré los links al final del post. Sorry!

Como hemos dicho, primero tendríamos que preparar las tasks que vamos a usar con gulp, en el gist de más abajo podemos ver la task para browserify.

Hagamos un stop para ver que pug templates vamos a usar en nuestra aplicación. Vamos a tener un layout compartido entre todas las páginas que básicamente van a ser 3:

  • Home, donde tendremos un formulario para buscar en Imdb y en donde mostraremos los resultados de esa búsqueda.
  • Movies o Series, donde mostraremos todas las series o movies que tengamos en nuestra base de datos.
  • Serie o movie, donde mostraremos la información de una sola movie o serie.

Este vendría a ser el layout de nuestra app:

Antes de ver que módulos vamos a crear podemos entrever que en nuestra aplicación hay una clara pauta que se repite. Tanto al buscar en Imdb como mostrar nuestras colecciones o mostrar un simple item, todo se reduce a enseñar un título, carátula e información a cerca de la serie o movie. Es por eso que con crear una sola directiva de Angular vamos a poder simplificar y abstractar mucho nuestra app.

La directiva va a tener 3 propiedades:
1. data, la data a usar
2. callAction, específica función a ejecutar en su contexto
3. textBtn, texto a mostrar en ese botón

La directiva la usaremos de la siguiente manera y con tan solo cambiar el valor de esas propiedades la podemos reusar bastante:

imdb-card(data="albert.data", call-action="albert.goToSleep()" text-btn="Now")

En esta app va a ser usada en 3 sitios:

  1. En /,
    1. imdb-card(data="home.imdbData", call-action="home.saveToDb()" text-btn="Save")
  2. En imdb/:collection/
    1. imdb-card(ng-repeat="imdbItem in imdb.collection", data="imdbItem", call-action="imdb.deleteItem( $index )" text-btn="Delete")
  3. En imdb/:collection/:id
    1. imdb-card(data="imdb.singleItem", call-action="imdb.showForm()" text-btn="Add Rating")

Para ejecutar Gulp es tan sencillo como escribir gulp en tu terminal pero tendrás que tener en cuenta unas cuantas cosas sobre como usar la app:

  1. Tendrás que usar gulp si o si para crear (bundle) el archivo que contendrá todo tu código de Angular
  2. Mientras que estás desarrollando tu app, Gulp creará un servidor web (localhost:9000) que actualizará tu navegador cada vez que hagas un cambio en los archivos.
  3. Para empezar el verdadero servidor de tu app tendrás que ejecutar el comando npm start y visitar localhost:3000 en tu navegador.

    Como puedes ver en la task el punto de entrada de nuestro aplicación será router.js donde usaremos require('./module') para requerir todos los módulos que vamos a usar. En este archivo también se especifica que controller y template va a usar cada ruta de nuestra app. Como puedes comprobar estas rutas son las mismas que hemos especificado en nuestro servidor como getters

Nuestra App va a tener un módulo/Controller para la página de inicio, el cual se encargará de hacer la búsqueda en imdb y guardar en la base de datos y otro módulo para controlar las colecciones de series o movies. Este segundo módulo también gestionará el actualizar y eliminar items en la base de datos. Ya en el gist de abajo te puedes dar cuenta de la libertad que te da browserify y usar node a la hora de crear cualquier cosa que sea en javascript.

La manera de organizar el proyecto es creando diferentes módulos donde cada uno tendrá su funcionalidad, los cuales importamos donde hagan falta al estilo "node" y es Angular quien se encargará de inyectarlas en sus respectivos módulos, al fin y al cabo estamos hablando de funciones.

Ahora nos hace falta ver de que se compone nuestro imdbService, el encargado de hacer las http requests y la lógica para el homeController.

Nuestro servicio básicamente usa los endpoints/urls que hemos definido en nuestra API, ahora como que todo empieza a encajar y tener sentido :)

Como puedes ver, frontend de nuestra app tan solo se dedica a pedir información al servidor y mostrarla, todo el trabajo duro lo hará el servidor. En homeController usamos contentReady como feedback al usuario mientras las Promises se resuelven y aún no tenemos contenido que mostrar. Por ejemplo este es el recorrido que hace la búsqueda en imdb:

  1. Rellenamos y enviamos el formulario con un nombre (castle) y su tipo (serie),
  2. service.getImdbData( this.imdbText, this.imdbType ) entra en acción enviando un post request al servidor a esta URL ./api/search?q=castle&t=serie
  3. en el servidor a tráves de esa URL extraemos los parámetros a usar en
  4. resolveImdbCall(req.query.q, req.query.t) que ejecutará una Pomise para buscar en Imdb la información que le hemos pedido
  5. una vez conseguida la información la retornamos al cliente donde la Promise que devuelve service.getImdbData quedará resuelta y podremos mostrar esa info al usuario.

Ahora si el usuario quiere podrá guardar esa búsqueda en la base de datos. He añadido un paquete angular-ui-notification para notificar al usuario en forma de feedback cuando algo ocurra en nuestra app, ya sea un mensaje de éxito como de error al salvar, eliminar o actualizar items.

El tutorial ya va llegando a su fin :( Para la última parte vamos a ver como re-usar un controller en Angular, es decir con el mismo controller vamos a mostrar las movies o series dependiendo de que parámetro estemos usando.

Como hemos hecho antes miremos el recorrido que hace nuestra request para llegar a mostrar la colección que queremos:

  1. en la página de inicio clickamos al link de las movies,
  2. service.getAPIData( $routeParams.collection ) entra en acción y hace un GET request a la API,
  3. concretamente a esta ruta '/api/all/:imdb' donde imdb será el valor de $routeParams.collection, en este caso es movie
  4. internamente la API esta ejecutando una query a la base de datos usando este método Imdb.find({ type: req.params.imdb })
  5. el cual devolverá un Array con todas las movies,
  6. cuando service.getAPIData termine de ejecutarse (la Promise se resuelva) el usuario ya podrá ver en pantalla toda su colección de movies.

Los métodos tendrían un aspecto similar al siguiente:

Vayamos a poner todas las piezas juntas para el controller del que estamos hablando, es decir ImdbController.

Con el gist de arriba ya podemos enseñar tanto nuestra colección de movies como de series, ya solo queda ver como mostramos cada movie o serie y como añadir nuestro propio rating.

Una vez se resuelva la Promise que devuelve la colección especificada vamos a añadirle a cada elemento la url donde mostraremos su propia página con su información (línea 18), value.itemurl = '/imdb/${$routeParams.collection}/${value._id}';

Ahora al clickar en el título una nueva página se abrirá con una url similar a esta imdb/movie/349292ej422022,tal vez podríamos estar usando el nombre de la movie en vez de su _id pero habría que mirar como substituir espacios en blanco por -.

// algo así
const newTitle = imdb.title.split(' ').join('-');

El gist de abajo es la template que usamos para mostrar cada item de la colección, verás que tenemos un formulario en el cual vamos a poder añadir nuestro propio ranking a cada elemento de la colección.

El proceso para actualizar el ranking viene a ser un poco el mismo que hemos estado haciendo hasta ahora (línea 52 del gist meanImdbController.js):

  1. al enviar el formulario usamos service.updateItem( $routeParams.id, $imdb.rating )
  2. hacemos un POST request al endpoint ./api/update/${id} donde hacemos una query a la base de datos,
  3. Imdb.findOne({ _id: req.params.id }) nos encuentra el elemento que queremos actualizar
  4. le añadimos el rating del formulario movie.setMyRating( req.body.rating );
  5. para terminar de resolver la Promise en service.updateItem y mostrar al usuario la movie o serie actualizada

Ya está! ya hemos creado una app usando el MEAN stack...

Lo prometido es deuda, aquí tienes los links de los archivos mencionados antes:

  1. Configuración Angular tests

  2. Angular tests

  3. Archivo gulpfile.js

  4. Tasks para gulp

Aquí tienes el enlace a Github con todo el contenido. Puede que en los gist hayan errores, sobretodo porque he ido recortando trozos para no hacer el post tan largo. 🙈

Obviamente te puedes ahorrar el escribir todo este montón de archivos e utilizar algunos de los paquetes npm que ya corren por allí, eso si siempre he creído que muchos de estos paquetes crean complexidad e instalan cosas que nunca usarás. Pero si tienes prisa para hacer el proyecto son sin duda una buena opción:

  1. http://mean.io/
  2. https://www.npmjs.com/package/generator-mean-stack
  3. https://www.npmjs.com/package/meanstack
  4. http://meanjs.org/generator.html
  5. ....... por nombrar algunos

Bueno espero que os haya servido de ayuda, a mí particularmente me ha servido para consolidar un poco más mis conocimientos sobre como testear y crear una API.

Si el MEAN stack os ha dejado un buen sabor de boca y creéis que es algo que tenéis que aprender si o si estáis de suerte, bueno si sois de Barcelona o Madrid ya que es el stack tecnológico que han escogido en Ironhack para hacer sus bootcamps dedicados al web development.

Stay tuned! Que me quiten el teclado de las manos!

comments powered by Disqus