Skip to content

Latest commit

 

History

History
1499 lines (1159 loc) · 42.1 KB

README.md

File metadata and controls

1499 lines (1159 loc) · 42.1 KB

Clase 1: Node.js

JavaScript

js_logo

JavaScript (abreviado comúnmente JS) es un lenguaje de programación interpretado, dialecto del estándar ECMAScript. Se define como orientado a objetos, basado en prototipos, imperativo, débilmente tipado y dinámico.

Se utiliza principalmente en su forma del lado del cliente (client-side), implementado como parte de un navegador web permitiendo mejoras en la interfaz de usuario y páginas web dinámicas aunque existe una forma de JavaScript del lado del servidor (Server-side JavaScript o SSJS). Su uso en aplicaciones externas a la web, por ejemplo en documentos PDF, aplicaciones de escritorio (mayoritariamente widgets) es también significativo. JavaScript Wikiwand

Características

  • Multiparadigma
  • Imperativo y estructurado
  • Dinámico
    • Tipado dinámico
    • Objetual
    • Evaluación en tiempo de ejecución
  • Funcional
    • Funciones de primera clase
  • Prototípico
    • Prototipos
    • Funciones constructoras
  • Entorno de ejecución
  • Funciones como métodos
  • Arrays y la definición literal de objetos
  • Expresiones regulares

¿Necesito un Polyfill?

Compiladores

compiler

Transpiladores

babel

Node.js

Node_logo

Node.js es un entorno en tiempo de ejecución multiplataforma, de código abierto, para la capa del servidor (pero no limitándose a ello) basado en el lenguaje de programación ECMAScript, asíncrono, con I/O de datos en una arquitectura orientada a eventos y basado en el motor V8 de Google. Fue creado con el enfoque de ser útil en la creación de programas de red altamente escalables, como por ejemplo, servidores web. Fue creado por Ryan Dahl en 2009 y su evolución está apadrinada por la empresa Joyent, que además tiene contratado a Dahl en plantilla - Wikipedia

Un poco diferente a un servidor tradicional

bloqueo

Puntos Fuertes

  • Asincronía (no bloqueo)
  • Backend completo
  • Gestión de paquetes a través de npm (comunidad)
  • Single thread (paralelismo)
  • Librerías propias
  • Utilidades
  • Código abierto
  • Basado en el V8 (escrito en C++) de Google
  • Multiplataforma
  • Orientado a Eventos
  • No se limita solo a servidores HTTP

¿Es oro todo lo que reluce?

Dependencias, dependencias, dependencias... y más dependencias

dependency-hell

Librerías interesantes

El ecosistema de Node.js

nodejs

Un poco de repaso...

APIs

Una API representa la capacidad de comunicación entre componentes de software.

CRUD

CRUD

  • Create (POST):
    • Respuesta 200 - OK
    • Respuesta 204 - Sin contenido
    • Respuesta 404 - No encontrado
    • Respuesta 409 - Conflicto, ya existe
  • Read (GET):
    • Respuesta 200 - OK
    • Respuesta 404 - No encontrado
  • Update (PUT):
    • Respuesta 200 - OK
    • Respuesta 204 - Sin contenido
    • Respuesta 404 - No encontrado
  • Delete (DELETE):
    • Respuesta 200 - OK
    • Respuesta 404 - No encontrado

Códigos de respuesta HTTP:

  • 1xx Informativas
  • 2xx Peticiones Correctas
  • 3xx Redirecciones
  • 4xx Errores Cliente
  • 5xx Errores Servidor

Lista de respuestas HTTP

Especificación

Ejemplos:

Programación dirigida a eventos

La programación dirigida por eventos es un paradigma de programación en el que tanto la estructura como la ejecución de los programas van determinados por los sucesos que ocurran en el sistema, definidos por el usuario o que ellos mismos provoquen.

events

Para entender la programación dirigida por eventos, podemos oponerla a lo que no es: mientras en la programación secuencial (o estructurada) es el programador el que define cuál va a ser el flujo del programa, en la programación dirigida por eventos será el propio usuario —o lo que sea que esté accionando el programa— el que dirija el flujo del programa. Aunque en la programación secuencial puede haber intervención de un agente externo al programa, estas intervenciones ocurrirán cuando el programador lo haya determinado, y no en cualquier momento como puede ser en el caso de la programación dirigida por eventos. Wikiwand

  • Ejemplo:
const { EventEmitter } = require('events');

const emitter = new EventEmitter();
// Cuando se emita el evento "talk" ejecutamos un "console.log"
emitter.addListener('talk', console.log);
// Cada segundo emitimos el evento "talk" con el texto "Hi!"
setInterval(() => emitter.emit('talk', 'Hi!'), 1000);

Multipurpose Internet Mail Extensions (MIME)

Más usadas::

  • text/html
  • text/plain
  • text/css
  • image/gif
  • image/x-png
  • image/jpeg
  • application/pdf
  • application/zip
  • text/javascript

Lista Completa

Línea de comandos

Versiones:

  • Pares -> Estables
  • Impares -> Inestables
  • LTS -> Long Term Service (recomendables)

Roadmap

Instalación:

  • Con gestor de versiones (recomendada): NVM
  • Con instalador: Node.js

Comprobamos que funciona correctamente comprobando la versión instalada:

node -v

El típico: Hello World!

console.log('Hello World!');

Librerías Nativas (CORE)

Los estados de la API:

  • 0: Deprecated
  • 1: Experimental
  • 2: Stable

Algunos de los módulos:

  • Assertion testing
  • Buffer - Permite el trabajo con datos binarios
  • C/C++ Addons - Permite integrar librerias de C/C++
  • Child Processes - Permite crear y gestionar subprocesos
  • Cluster - Permite gestionar nuestro proceso principal e "hijos" entre diversos módulos
  • Command Line Options - Controla el lanzamiento de Node por Consola
  • Console - Permite trabajar con la consola (terminal), imitando la consola del navegador
  • Crypto - Relacionado a las funcionalidades de criptografía necesarias apra algunso protocolos como SSL
  • Debugger - Utilidades de depuración
  • DNS - Gestion y resolución de nombres de Dominios
  • Domain - DEPRECIADO
  • Errors - Gestión de errores
  • Events - Permite gestionar y crear eventos
  • File System - Permite manipular y crear ficheros en el sistema
  • Globals - Ámbito global
  • HTTP - Gestión del protocolo HTTP
  • HTTPS - Gestión del protocolo HTTPS (http y tls/ssl)
  • Modules - Gestión y carga de módulos
  • Net - Nos aporta una capa de red asíncrona y permite gestionar "streams" tanto cliente como servidor.
  • OS - Información básica sobre el sistema operativo en el que estamos funcionando
  • Path - Gestión de rutas dentro del sistema (navegación de carpetas y archivos)
  • Process - Objeto global que gestiona el proceso del sistema que representa nuestra ejecución de Node
  • Punycode - Sintaxís de codificación a RFC 3492 y RFC 5891
  • Query Strings - Manipualción y gestion de cadenas URL
  • Readline - Puede leer línea a línea información de entrada como la consola
  • REPL - Read-Eval-Print-Loop (REPL)
  • Stream - Interfaz abstracta usada por otros módulos para gestionar el flujo de la información
  • Timers - Funciones globales de tiempo como setInterval, clearInterval, etc...
  • TLS/SSL - Capa de encriptación basada en OpenSSL
  • UDP/Datagram - Gestión del protocolo UDP
  • URL - Facilita la resolución y parseo de URLs
  • Utilities - Utilidades varias, la mayoría depreciadas
  • V8 - Información sobre v8
  • VM - Permite aislar código en "sandboxes"
  • ZLIB - Permite trabajar con Gzip/Gunzip, Deflate/Inflate y DeflateRaw/InflateRaw

HTTP

Hello World con HTTP:

const http = require('http');

http.createServer((req, res) => {
    res.writeHead(200, {
	'Content-Type': 'text/plain'
    });
    res.end('Hello World!');
}).listen(8080, 'localhost');

Redireccionamiento:

const http = require('http');

http.createServer((req, res) => {
    res.writeHead(301, {
	'Location': 'http://www.google.es/'
    });
    res.end();
}).listen(8080, 'localhost');

Peticiones asíncronas (simulando el comando ping):

const http = require('http');

// Ping
function ping(host) {
    http.get(host, (response) => {
        console.log(`La respuesta de ${host} es ${response.statusCode}`)
    }).on('error', (e) => {
        console.log('Tenemos un error!! ', e.message);
    });
}

ping('http://www.google.com');

LLamada a una API para obtener un JSON:

const http = require('http');

function getJSON(endpoint) {
    return new Promise((resolve, reject) => {
	http.get(endpoint, (response) => {
	    let data = '';

	    response.on('data', (chunk) => {
		data += chunk;
	    });
	    response.on('end', () => {
		try {
		    data = JSON.parse(data);
		    resolve(data);
		} catch(error) {
		    reject(error);
		}
	    });
	}).on('error', reject);
    });
}

getJSON('http://ghibliapi.herokuapp.com/films/').then(console.log, console.error);

URL

Anatomía de una URL:

url_parts

Leyendo urls:

const url = require('url');

const demoURL = 'http://localhost:3000/ruta?parametro=dato#detalle';
const parsedUrl = url.parse(demoURL, true);

console.log(`Host: ${parsedUrl.hostname}`);
console.log(`Puerto: ${parsedUrl.port}`);
console.log(`Ruta: ${parsedUrl.pathname}`);
console.log(`Parámetros: ${JSON.stringify(parsedUrl.query)}`);
console.log(`Hash: ${parsedUrl.hash}`);
const { URL } = require('url');

const demoURL = new URL("http://localhost:3000/ruta?parametro=dato#detalle");

console.log(`Host: ${demoURL.hostname}`);
console.log(`Puerto: ${demoURL.port}`);
console.log(`Ruta: ${demoURL.pathname}`);
console.log(`Parámetros: ${demoURL.searchParams}`);
console.log(`Hash: ${demoURL.hash}`);

Trabajando con rutas:

const http = require('http');
const url = require('url');

http.createServer((req, res) => {
  const pathname = url.parse(req.url).pathname;

  if (pathname === '/') {
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    res.end('Index!');
  } else if (pathname === '/other') {
    res.writeHead(200, {
      'Content-Type': 'text/plain; charset=utf-8'
    });
    res.end('Just a page');
  } else if (pathname === '/redirect') {
    res.writeHead(301, {
      Location: '/'
    });
    res.end();
  } else {
    res.writeHead(404, {
      'Content-Type': 'text/plain'
    });
    res.end('404!');
  }
}).listen(8080, 'localhost');

Un pequeño inciso: Asincronía:

El modelo de programación de Node.js es monohilo, asíncrono y dirigido por eventos.

  1. No puede haber código bloqueante o todo el servidor quedará bloqueado y esto incluye no responder a nuevas peticiones entrantes.
  2. La asincronicidad implica que no sabemos cuándo ni en que orden se va a ejecutar el código, generalmente esto no es importante pero en ocasiones sí lo es y habrá que tenerlo en cuenta.
  3. En caso de error inesperado debemos capturarlo y controlar el posible estado en que haya podido quedar la ejecución del código.

Nicolas Nombela en nnombela

Síncrono - código bloqueante:

const http = require('http');

let count = 1;

function writeResponse(response) {
  response.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  response.end(`Request ended: ${count}`);
  console.log('Request ended... ', count);
}

function sleepSynch(seconds, response) {
  const startTime = new Date().getTime();
  
  while (new Date().getTime() < startTime + seconds) {
    // Nothing happens....
  }
  writeResponse(response);
}

http.createServer(function(request, response) {
  console.log('Request started... ', count);
  sleepSynch(3000, response);
  count++;
}).listen(8080);

Asíncrono (setTimeOut()):

const http = require('http');

let count = 1;

function writeResponse(response, i) {
  response.writeHead(200, { 'Content-Type': 'text/plain' });
  response.end(`Request ended: ${i}`);
  console.log('Request ended... ', i);
}

http.createServer((request, response) => {
  const i = count++;

  console.log('Request started... ', i);
  setTimeout(() => {
    writeResponse(response, i);
  }, 3000);
}).listen(8080);

File System

Leer un archivo:

const fs = require('fs');

fs.readFile('archivo.txt', 'utf8', (err, data) => {
  if (!err) {
    console.log(data);
  } else {
    throw err;
  }
});

Escribir un archivo:

const fs = require('fs');

const filename = 'test.txt';
const data = `This will be saved in '${filename}'`;

fs.writeFile(filename, data, (err) => {
  if (!err) {
    console.log(`Data saved in '${filename}'`);
  } else {
    throw err;
  }
});

Usando Promesas y Callbacks:

const fs = require('fs');

// Con CallBacks!
fs.readFile('./README.md', (error, content) => {
  console.log('Reading the file...');
  fs.writeFile('./length.txt', content.length, (error) => {
    if (error) {
      console.log('Error! ', error);
    } else {
      console.log('File has been created!');
    }
  });
});

// With Promises!
function readFilePromise(file) {
  return new Promise((resolve, reject) => {
    console.log(`Reading the file '${file}'`);
    fs.readFile(file, (error, content) => {
      if (error) {
        console.log('Error! ', error);
        return reject(error);
      }
      console.log(`File '${file}' has been readed!`);
      resolve(content);
    });
  });
}

function writeFilePromise(file, content) {
  return new Promise((resolve, reject) => {
    console.log(`Writing the file '${file}'`);
    fs.writeFile(file, content, (error) => {
      if(error) {
	console.log('Error! ', error);
	return reject(error);
      }
      console.log(`File '${file}' has been writed!`);
      resolve();
    });
  });
 }

//Opción1
readFilePromise('./README.md').then((content) => {
  writeFilePromise('./COPY_README.md', content);
}, (error) => {
  console.log('Error! ', error);
});

//Opción2
Promise.all([
  readFilePromise('./others.txt'),
  readFilePromise('./users.txt'),
  readFilePromise('./more-things.txt')
]).then((responses) => {
  console.log(`The first file has ${respuestas[0].length} characters`);
  console.log(`El segundo tiene ${respuestas[1].length} characters`);
  console.log(`El tercero tiene ${respuestas[2].length} characters`);
});

//Opcion3
Promise.race([
  readFilePromise('./others.txt'),
  readFilePromise('./users.txt'),
  readFilePromise('./more-things.txt')
]).then((response) => {
  console.log(`The fastest file to read has ${response.length} characters`);
});

Utilizando APIS nativas como promesas:

const fs = require('fs');
const { promisify } = require('util');

const readFile = promisify(fs.readFile);

// Con CallBacks!
readFile('./README.md').then((content) => {
  console.log(`The file has ${content.length} characters`);
}, (error) => {
  console.log('Error! ', error);
});

API oficial basada en promesas (a partir de node v12)

Más métodos para::

  • Síncronos
  • Escucha de cambios
  • Manipulación de carpetas
  • Comprobación de ficheros/directorios
  • etc...

Events

Servidor HTTP y eventos:

  • Sin eventos
const http = require('http');

const server = http.createServer((request, response) => {
  response.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  response.end('Hi!');
}).listen(8080);
  • Con eventos
const http = require('http');

const server = http.createServer().listen(8080);

server.on('request', (request, response) => {
  response.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  response.end('Hi!');
});

Creando nuestros propios listeners:

const { EventEmitter } = require('events');

const ee = new EventEmitter();

ee.on('current-date', (date) => {
  console.log(date);
});

setInterval(() => {
  ee.emit('current-date', Date.now());
}, 500);

Ejemplo: Juguemos al Ping-Pong:

const { EventEmitter } = require('events');

const pingPong = new EventEmitter();

pingPong.on('ping', () => {
  console.log('Ping!');
  setTimeout(() => {
    pingPong.emit('pong');
  }, 1000);
});

pingPong.on('pong', () => {
  console.log('Pong!');
  setTimeout(() => {
    pingPong.emit('ping');
  }, 1000);
});

pingPong.emit('ping');

Heredando de EventEmitter:

const { EventEmitter } = require('events');

class Foo extends EventEmitter {
	constructor() {
		super();

		setInterval(() => {
			this.emit('say-hello');
		}, 1000);
	}
}

const foo = new Foo();

foo.on('say-hello', () => console.log('Hello!'));
Node.js Event Loop

loop

Arquitecura diferente

Single Thread

Multi Thread

Process

Códigos de salida en Node.js:

  • 0: OK
  • 1: Uncaught Fatal Exception - No ha podido ser capturada
  • 2: Unused (reserved by Bash for builtin misuse)
  • 3: Internal JavaScript Parse Error
  • 4: Internal JavaScript Evaluation Failure
  • 5: Fatal Error - There was a fatal unrecoverable error in V8.
  • 6: Non-function Internal Exception Handler
  • 7: Internal Exception Handler Run-Time Failure
  • 8: Unused
  • 9: Invalid Argument
  • 10: Internal JavaScript Run-Time Failure
  • 11: Invalid Debug Argument
  • 12: Signal Exits - El sistema operativo acaba con Node.

process.argv:

console.log(process.argv)
/*
* 1. ubicacion de node (bin)
* 2. ubicación del script (location)
* 3. [Otros parametros]
*/

Detalles del sistema:

process.argv[2] = process.argv[2] || "Node.js funcionando desde C9.io";

console.log("===================================");
console.log("Id: " + process.pid);
console.log("Título: " + process.title);
console.log("Ruta: " + process.execPath);
console.log("Directorio Actual: " + process.cwd());
console.log("Node Versión: " + process.version);
console.log("Plataforma: " + process.platform);
console.log("Arquitectura: " + process.arch);
console.log("Tiempo activo: " + process.uptime());
console.log("Versiones Dependencias: ");
process.argv.forEach((val, index, array) => {
  console.log(`${index}: ${val}`);
});
console.log("===================================");

Librerías para crear programas ejecutables:

console.log(Hi!):

process.stdout.write(`Hi!\n`);

Captura de errores inesperados:

process.on('uncaughtException', (error) => {
  console.error(error.stack);
});

setTimeout(() => {
  throw new Error('foo')
}, 1000);

Ejecucción de tareas antes de la finalización del proceso:

process.on('exit', function (err) {
  // Limpiar cache.. y cosas similares...
});

Creando ficheros ejecutables

  • Solo para entornos UNIX
  • Necesitamos shebang
  • Hacer el script ejecutable (chmod +x my_file.js)
  • Ejecutar el fichero (./my_file.js)

Shebang es, en la jerga de Unix, el nombre que recibe el par de caracteres #! que se encuentran al inicio de los programas ejecutables interpretados.

Creamos el fichero

#!/usr/bin/env node
console.log('Hello World!');

Cluster

Cada instancia de Node.js se ejecuta en un único hilo. Para sacarle partido a los procesadores de varios núcleos utilizaremos la librería cluster.

Creando un servidor sin utilizar clusters:

const http = require('http');
const url = require('url');

const server = http.createServer().listen(8080);

server.on('request', (req, res) => {
  const pathname = url.parse(req.url).pathname;

  if (pathname === '/kill') {
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    res.end('Has matado el monohilo. PID: ' + process.pid);
    process.exit(0);
  } else {
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    res.end('Hola desde el monohilo. PID: ' + process.pid);
  }
});

Creando un servidor utilizando clusters:

  • El proceso hijo que cae se vuelve a levantar.
  • El proceso padre se mantiene "separado"
const cluster = require('cluster');
const http = require('http');
const url = require('url');
const cpus = require('os').cpus().length; // nproc

if (cluster.isMaster) {
  console.log('Proceso maestro con PID:', process.pid);

  for (let i = 0; i < cpus; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker) => {
    console.log('hijo con PID ' + worker.process.pid + ' muerto');
    cluster.fork();
  });

} else {
  console.log('Arrancado hijo con PID:', process.pid);

  const server = http.createServer().listen(process.env.PORT);

  server.on('request', (req, res) => {
    const pathname = url.parse(req.url).pathname;
    if (pathname === '/kill') {
      res.writeHead(200, {
          'Content-Type': 'text/plain'
      });
      res.end('Has matado al proceso hijo ' + process.pid);
      process.exit(0);
    } else {
      res.writeHead(200, {
          'Content-Type': 'text/plain'
      });
      res.end('Hola desde ' + process.pid);
    }
  });
}

Taking Advantage of Multi-Processor Environments in Node.js

Librerias::

Buffer

  • Nos ofrece la posibilidad de alamacenar datos sin procesar
  • Una vez iniciados no puede modificarse el tamaño
  • Tamaño máximo 1GB
const buf4 = Buffer.from('ñam ñam', 'utf-8');

console.log(buf4)
console.log(buf4.toString('utf-8'))

Stream

  • Se utilizan para gestionar un flujo de datos
  • Muy usados por librerías y módulos (ej. gulp)
  • Capa de abstracción para operaciones con datos
  • Lógica de tuberias (cadena de procesos)
  • Gestiona el buffer por si mismo

Tipos:

  • Readable (Lectura): Eventos (data, error, end)
  • Writable (Escritura): Eventos (drain, error, finish)
  • Duplex (Ambos)
  • Transform (Transformación de datos)

Función .pipe():

  • Simple: origen.pipe(destino);
  • Concatenando: origen.pipe(destino).pipe(otroDestino);

Ejemplo: Stream multimedia:

const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
  const song = 'song.mp3';
  const stat = fs.statSync(song);

  res.writeHead(200, {
    'Content-Type': 'audio/mpeg',
    'Content-Length': stat.size
  });

  const readableStream = fs.createReadStream(song);
  readableStream.pipe(res);
}).listen(8080);

Streams y ficheros:

const fs = require('fs');

const lectura = fs.createReadStream('README.md');
const escritura = fs.createWriteStream('COPY.md');

lectura.pipe(escritura);

Comprueba como el tiempo de respuesta es menor utilizando streams:

const http = require('http');
const fs = require('fs');
const url = require('url');

function createBigFile() {
  const file = fs.createWriteStream('bigfile.txt');

  for(let i=0; i<= 1e6; i++) {
    file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n');
  }

  file.end();
}

createBigFile();

http.createServer((request, response) => {
  const { pathname } = url.parse(request.url);

  if (pathname === '/stream') {
    const bigfile = fs.createReadStream('bigfile.txt');

    bigfile.pipe(response);
  } else {
    fs.readFile('bigfile.txt', 'utf-8', (err, data) => {
      response.end(data);
    });
  }
}).listen(8080);

Child Process

  • Ideal para tareas pesadas, inestables o muy lentas.
  • Nos permite usar comandos del sistema.
  • Podemos lanzar aplicaciones basadas en otros lenguajes o sistemas.

spawn devuelve un stream:

const spawn = require('child_process').spawn;
const ping = spawn('ping', ['fictizia.com']);

ping.stdout.setEncoding('utf8');
ping.stdout.on('data', console.log);

exec retorna un buffer:

const { exec } = require('child_process');

  // cat solo funciona en UNIX
exec('cat README.md', (err, stdout, stderr) => {
  if(!err){
    console.log('El contenido de nuestro archivo', stdout)
  } else {
    console.log('Error: '+err)
  }
});

Variables del Entorno

¿Cómo ver todas las variables disponibles en el entorno?

  • Windows: SET
  • UNIX: env

Crear nuevas variables en el entorno:

  • Windows: SET FOO='Hi!'
  • UNIX: export FOO='Hi!'

Recuperar las variables con Node.js:

const datoRecuperado = process.env.ALGO;
console.log(process.env.ALGO);

Creando variables de entorno limitadas al programa que se va a ejecutar (SOLO UNIX):

FOO='Hi!' NODE_ENV=production node app.js

¿Cómo recuperar los datos desde el proceso de Node.js?:

if(process.env.NODE_ENV === "production"){
  console.log("Entramos en modo producción");
} else if (process.env.NODE_ENV === "development"){
  console.log("Entramos en modo desarrollo");
} else {
  console.log("Entramos en modo desconocido. ¡Revisa las variables del entorno!");
}

Alternativas:

Modularización

  • Especificación de CommonJS
  • Exports es un objeto que vamos "rellenando"
  • La asignacion al exports es inmediata. No se pueden usar callbacks o similares
  • No es necesario usar module.exports ya es que es global (const exports = module.exports = {};)
  • Es importante controlar la reasignación de module.exports

Exportar los datos:

// config.js
const config = {
  token: "XXXXXXX",
};

module.exports = config;

Importar los datos:

// app.js
const config = require('./config');

console.log(config.token);

El gestor de paquetes por defecto: NPM

npm_logo

Comprobar versión:

npm -v

Instalar paquetes:

# Global
npm install -g <package_name>

# Local
npm install <package_name>

# Latest version
npm install <paquete>@latest

# Some version
npm install <package_name>@1.2.3

Buscar paquetes:

npm search <paquete>

Información de los paquetes:

npm view <paquete>

Lista de paquetes instalados:

# Global
npm ls -g

# Local
npm ls

Más comandos - CLI

npx

npx

A partir de la versión v5.2.0 de npm se incluye un nuevo binario llamado npx.

Al igual que npm permite manejar las dependencias de un proyecto a través del repositorio de paquetes, con npx también vamos a poder manejar ejecutables que no tengamos instalados en nuestra máquina. Si ejecutamos un comando a través de npx (ej. npx cowsay "Hola") se intentará ejecutar dicho paquete desde nuestro directorio actual, en el caso de que no se encuentre descargado se descargará automáticamente y se ejecutará.

Un uso común que se le puede dar es el de ejecutar paquetes que normalmente no utilizamos a menudo (por ejemplo yeoman o create-react-app), como pueden ser generadores de código o paquetes que no queramos descargar para ocupar sitio en el disco.

También se puede utilizar para invocar paquetes en otras versiones de node:

# Con "-p" añadimos un paquete a "$PATH"
npx -p node@8 npx cowsay "Hola"
# Descargamos cowsay y lolcatjs, a continuación hacemos un "echo"
# y la salida se la pasamos a "cowsay" y "lolcat"
npx -p cowsay -p lolcatjs -c 'echo "$npm_package_name@$npm_package_version" | cowsay | lolcatjs'

Definición del proyecto, el fichero package.json

  • Datos proyecto
  • Tareas
  • Dependencias (dependencies y devDependencies)
  • Documentación

Anatomía del fichero:

{
  "name": "hello-alligator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

¿Cómo se crea un proyecto?

npm init

Guardar nuevas dependencias:

# Dependency
npm install <package_name>

# Development dependency
npm install <package_name> --save-dev

Semantic Versioning:

  • Estructura -> X.Y.Z-Extra
  • Cambio Mayor - No retrocompatible
  • Cambio Menor - Retrocompatible - Nuevas funcionaldiades o cambios
  • Parche - Retrocompatible - Solución de errores
  • Extras - Indicativos o versiones especiales (Beta, Alfa, x86, etc...)

npm scripts (comandos de CLI)

Añadiendo comandos:

"scripts": {
  "test": "npm -v",
  "start": "node -v",
  "hello": "echo 'Hello World!'"
}

Mostrando todos los comandos:

npm run

Ejecutando comandos::

# Default commands
npm test

# Custom commands
npm run hello

Ejercicios

1 - Crea las rutas básicas para tener una página web clásica:

  • Debe responder con contenido HTML
  • /: Mostrará un título (h1) con el texto Bienvenido
  • /about: Se mostrará un pequeño texto introductorio
  • /image: Mostrará la siguiente imagen sin utilizar HTML (Utiliza los tipos MIME y la librería https)
  • En el caso de que la ruta no exista, se redireccionará al index (/)

Solución:

const http = require('http');
const https = require('https');
const process = require('process');
const url = require('url');

http.createServer((req, res) => {
  const pathname = url.parse(req.url).pathname;
  const welcome = '<h1>Wellcome!!</h1>';
  const about = 'Some about text...';

  if (pathname === '/') {
    res.end('Welcome!');
  } else if (pathname === '/about') {
    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    });
    res.end(about);
  } else if (pathname === '/image') {
    res.writeHead(200, {
      'Content-Type': 'image/gif'
    });
    https.get('https://media1.giphy.com/media/13CoXDiaCcCoyk/giphy.gif', (imageResponse) => {
      imageResponse.pipe(res);
    });
  } else {
    res.writeHead(301, {
      Location: '/'
    });
  }
}).listen(8080);

2 - Crear un servidor web que al acceder te muestre el contenido del fichero que aparece en la url: Dada la siguiente url: http://localhost/README.md

  • Hay que extraer el nombre del fichero con la librería URL (README.md)
  • Leer el fichero con el FileSystem (fs):
    • Si el fichero no existe, devolver un error 404
    • Si el fichero existe, devolver el contenido del mismo

Solución:

const http = require('http');
const fs = require('fs');
const url = require('url');

http.createServer((request, response) => {
  const pathname = url.parse(request.url).pathname;
  const filename = pathname.substr(1);

  console.log(`Trying to find '${filename}'...`);
  fs.readFile(filename, (err, data) => {
    if (err) {
      response.writeHead(404, {
	'Content-Type': 'text/html'
      });
      response.write(`ERROR: Cannot find '${filename}'.`);
      console.log(`ERROR: Cannot find '${filename}'.`);
    } else {
      console.log(`Found '${filename}.`);
      response.writeHead(200, {
	'Content-Type': 'text/html'
      });
      response.write(data.toString());
    }
    response.end();
  });
}).listen(8080, 'localhost');

3 - Crear un servidor web con las siguientes características:

  • Al recibir una petición GET mostrará el texto Hola Mundo!
  • Si en la petición anterior llega el parámetro search buscará una película con ese valor utilizando omdbAPI
    • Puedes utilizar la api key e9b5e65a
    • Ejemplo: localhost?search=Avengers
  • Si hay resultados se mostrará el listado de películas
  • Si no hay resultados se devolverá un 404

Solución:

const http = require('http');
const url = require('url');

http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const { search } = parsedUrl.query;

  http.get(`http://www.omdbapi.com/?s=${search}&apikey=e9b5e65a`, (response) => {
    const { statusCode } = response;
    let rawData = '';

    response.setEncoding('utf8');
    response.on('data', (chunk) => rawData += chunk);
    response.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);

        if (!parsedData.Search.length) {
          throw new Error('No data :(');
        }

        res.writeHead(200);
        res.end(parsedData.Search.map((film) => film.Title).join('\n'));
      } catch(e) {
        res.writeHead(404);
        res.end('No data found :(');
      }
    });
  });
}).listen(8080);

4 - Realiza un script ejecutable que nos muestre la información de los personajes de la serie Rick & Morty.

  • Fuente de datos
  • Requisitos:
    • El script se debe ejecutar con cualquier cantidad de parámetros, donde cada uno será la id de un personaje (Ej. node my-script.js 1 2 3 4)
    • Si no se detecta el parámetro la aplicación debe cerrarse.
    • Ajustaremos la petición http en función del parámetro.
  • Apariencia (orientativa):

=============================
Morty Smith
-----------------------------
Status: Alive
Species: Human
Image: https://rickandmortyapi.com/api/character/avatar/2.jpeg
==============================

Solución:

#!/usr/bin/env node
const https = require('https');

const ids = process.argv.slice(2).join(',');

if (!ids) {
  console.error('Necesito al menos una ID como argumento');
  process.exit(1);
}

function renderCharacter(character) {
  console.log('=============================');
  console.log(character.name);
  console.log('-----------------------------');
  console.log('Status:', character.status);
  console.log('Species:', character.species);
  console.log('Image:', character.image);
  console.log('==============================');
}

https.get({
  host: 'rickandmortyapi.com',
  path: `/api/character/${ids}`
}, (res) => {
  let data = '';

  res.on('data', (chunk) => data += chunk);
  res.on('end', () => {
    let json = JSON.parse(data);

    if (!Array.isArray(json)) {
      json = [json];
    }

    json.forEach((character) => renderCharacter(character));
  });
}).on('error', (e) => {
  console.log('Error fetching data:', e.message);
  process.exit(1);
});

5 - Crearemos varios scripts para automatizar tareas utilizando npm:

  • npm run versions: Tiene que mostrar las versiones de nodejs y npm
  • npm run status: Verificador del status de Git
  • npm run curso: Clona nuestro curso de Github
  • npm run emoji: Muestra un emoji al azar utilizando emoji-random
  • npm run emoji: Muestra la url de un gif por consola make-me-lol
{
  "name": "npm-scripts-tasks",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "emoji": "emoji-random",
    "versions": "node -v && npm -v",
    "bootstrap": "git clone https://github.com/twbs/bootstrap.git",
    "curso": "git clone https://github.com/Fictizia/Curso-Node.js-para-desarrolladores-Front-end_ed5.git",
    "status": "git status",
    "lol": "make-me-lol --output --gif"
  },
  "devDependencies": {
    "emoji-random": "^0.1.2"
  },
  "author": "Ulises Gascon",
  "license": "ISC"
}

6 - Crea un script que te dibuje el árbol de directorios desde donde lo has ejecutado:

const fs = require('fs');
const path = require('path');

const PADDING = '  ';

function renderDir(dir, level) {
  console.log(PADDING.repeat(level) + path.basename(dir));
}

function drawTree(dir, level = 0) {
  const stats = fs.lstatSync(dir);

  if (stats.isDirectory()) {
    renderDir(dir, level);
    fs.readdirSync(dir)
      .map((filename) => path.join(dir, filename))
      .forEach((filepath) => drawTree(filepath, level + 1))
  } else if (stats.isFile()){
    renderDir(dir, level);
  }
}

drawTree(process.cwd());