tl;dr - En este enlace está el repositorio de GitHub para que explores el código.
package.json
de nuestro proyecto, con el siguiente contenido:
{ "name": "micro-platzi-profile", "main": "./index.js", "repository": "sergiodxa/micro-platzi-profile", "scripts": { "start": "micro -p 3000 ." }, "dependencies": { "isomorphic-fetch": "2.2.1", "jsdom": "9.4.2", "micro": "6.0.1" } }Luego, instalas todas las dependencias usando
npm install
.
Mientras lo hace, revisaremos qué definiste en el package.json:
lo primero es el nombre del proyecto; luego, el archivo principal (entry point), su repositorio en GitHub, y a continuación los scripts, donde tenemos la única entrada: start
. Éste iniciará micro en el puerto 3000, usando el archivo index.js,
donde estará el código del servidor.
En cuanto a las dependencias:
isomorphic-fetch:
Da soporte a Fetch API en Node.js.jsdom:
Permite tener un DOM falso en el servidor, con todos los métodos normales, basado en el string HTML que le indiques.micro:
La librería para crear microservicios que estamos aprendiendo a utilizar.const { env } = require('jsdom'); const fetch = require('isomorphic-fetch');
> Debido a que no vamos usar Babel, y micro no transpila nada de ES2015, tenemos que usar require
de CommonJS. Igualmente nada impide que se use Babel para soportar módulos de ES2015.<
jsdom
en una Promesa para facilitarnos su uso y no tener que usar callbacks:
/** * Promise wrapper for jsdom * @param {string} html The HTML string to use * @return {Object} The `window` object */ function jsdomPromised(html) { return new Promise((resolve, reject) => { env(html, [], (error, window) => { if (error) return reject(error); return resolve(window); }); }); }Ahora, programamos el scrapper: una función asíncrona que va a recibir un nombre de usuario y obtendrá el HTML del perfil de éste, para luego obtener el nombre, la descripción y la información de los cursos aprobados, y devolver un JSON con estos datos.
/** * Get user info from the user Profile * @param {string} username The user to scrap * @return {Object} The scrapped data */ async function scrapper(username) { // la url a donde vamos a hacer el request const url = `https://platzi.com/@${username}`; // hacemos el request con Fetch y obtenmos la respuesta const response = await fetch(url); // leemos la respuesta como text (esto nos da el HTML como un string) const html = await response.text(); // creamos nuestro DOM falso con `jsdom`, esto nos da un objeto `window` const window = await jsdomPromised(html); // y de este objeto obtenemos el objeto `document` const { document } = window; // obtenemos el nombre del usuario const name = document.querySelector('.ProfileHeader-name').innerHTML; // y su descripcion const description = document.querySelector('.ProfileHeader-description').innerHTML; // y por última su lista de cursos con el nombre, la carrera, el porcentaje y el badge const courses = Array .from(document.querySelectorAll('.Course')) .map($course => { // obtenemos el título del curso const title = $course.querySelector('.Course-title').childNodes[1].data; // la carrera a la que pertenece const career = $course.querySelector('.Course-career').innerHTML; // el porcentaje completado del curso const percentage = $course.querySelector('.Course-percentage').childNodes[1].data; // la url del badge const badgeUrl = $course.querySelector('.Course-badge > img').getAttribute('src'); return { title, career, percentage, badgeUrl }; }); // devolvemos un objeto con todos los datos que obtuvimos del usuario return { url, user: { username, name, description, }, courses, }; } module.exports = scrapper;
>> En el repositorio de Github tienes una versión más compleja, que devuelve aún más datos del usuario. <
require
:
const { parse } = require('url'); const scrapper = require('./lib/scrapper.js');Del módulo nativo de Node.js
url
,
nos vamos a traer el método parse
e importarás la función del scrapper
ya creado.
Ahora, crearás la función encargada de manejar las peticiones que lleguen.
const EXAMPLE_URL = 'https://micro-platzi-profile.now.sh?username=some-user-name'; module.exports = async request => { // parseamos la url para obtener la query (los parámetros que llegan como `?algo=valor`) const { query } = parse(request.url, true); // si no se envió un `username` vamos a devolver un error 400 if (!query.username) { // creamos el objeto de erro y definimos una propiedad `statusCode` const error = new ReferenceError( `You must query for a specific username using a URL like ${EXAMPLE_URL}.` ); error.statusCode = 400; throw error; } // en caso contrario, vamos a ejecutar el scrapper con el username recibido // y devolver la respuesta return await scrapper(query.username); };Esta simple y pequeña función será tu servidor HTTP. Como aparece en los comentarios del código, recibirá el request; obtendrá los parámetros de la query (validando que lleguen), y ejecutará el scrapper para obtener los datos y enviarlos en la respuesta. Nota como estamos exportando la función directamente, y en ningún lado importamos micro. Esto es porque este nos permite simplemente exportar una función asíncrona que haga
return
de la respuesta, o un throw
de un error. Al iniciar micro desde la terminal, este se encarga de importar nuestra función y crear el servidor en el puerto indicado.
De todas formas, micro posee métodos propios que podemos importar, y que te explicaré rápidamente:
micro(fn, { onError = null }):
Recibe la función del servidor (la que exportamos) y devuelve un servidor HTTP que puedes usar donde quieras. El onError
es una función que se ejecutará cuando recibe un error, y te permite manejar todos los errores.json(req, { limit = '1mb' }):
Este método recibe el objeto request
de la petición y te devuelve una promesa que, al completarse, te dará el body
de la petición HTTP. El limit
indica el límite de datos posibles a recibir en el body;
por defecto es 1 megabyte.send(res, statusCode, data = null):
Envía una respuesta, recibe el objeto response
(segundo parámetro de la función que exportaste), el código de status HTTP a usar y los datos a enviar. Si envías un objeto de JS, automáticamente lo convierte a un string JSON. De forma similar, se encarga solo de manejar los Stream
y Buffer
.sendError(req, res, error):
Recibe los objetos request,
response
y un objeto Error,
y envía el mensaje de error en la respuesta con el código de status HTTP definido en error.statusCode
(500, por defecto).createError(code, msg, orig):
Crea un objeto error con el statusCode
que le indiques, el mensaje y un orig
o error original si lo estás creando en base a otro error. Esta función abstrae lo que hiciste para definir el statusCode
en el servidor.now
de forma global usando npm.
Luego, simplemente desde la terminal ejecutas el comando now;
te pedirá un correo electrónico donde recibirás un enlace de confirmación. En cuanto entres, tendrás disponible un token de autenticación para no volver a ingresar tus credenciales, y comenzará el proceso de despliegue.
Podrás seguir todo el proceso en la terminal o ingresando a una URL que now
copia al portapapeles automáticamente. Si tomas esta última opción, al finalizar el sitio se recargará y verás el proyecto funcionando — o un mensaje de error, si algo salió mal.
Puedes entrar al ejemplo que trabajamos, que ya está funcionando en https://micro-platzi-profile.now.sh/?username=sergiodxa; si ingresas a https://micro-platzi-profile.now.sh/_src, puedes ver el código final (con scrapping de más datos). Este también está en el repositorio en GitHub.
Por último, si usamos Docker, podrías crear un Dockerfile
para meter en un container nuestro microservicio, y poder usar herramientas como Kubernetes para controlarlo.
Debido a que no vamos usar Babel, y micro no transpila nada de ES2015, tenemos que usar require de CommonJS. Igualmente nada impide que se use Babel para soportar módulos de ES2015.<
Sergio como sería este código utilizando las características de ES6?
hay algun curso sobre microservicios con node?