👉 Ver todas las notas
- Instalación
- ¿Qué es NodeJS?
- REPL
- Node nos permite escribir código asincrónico
- Leyendo argumentos
- File System
- Path
- HTTP (Server y requests)
- Módulos
- Windows: ir a la página oficial, descargar e instalar el binario de la versión LTS
- Linux / OS X: descargar e instalar el script nvm y luego instalar la versión LTS con el comando
nvm install --lts
Para chequear que se haya instalado correctamente, correr el comando node -v
en la consola (debe retornar una versión >= 12)
¿Qué es LTS? Leer LTS vs Current version
NodeJS ó Node a secas, es principalmente un entorno de ejecución, es decir, nos brinda el contexto necesario para poder ejecutar código JavaScript por fuera de un browser.
Es Open-Source y tiene soporte multi-plataforma.
Recordemos que JavaScript sólo funciona en el browser de forma nativa y que se trata de un lenguaje de alto nivel. Esto significa que realizar tareas como networking, acceder a hardware de red (ej: acceder a la tarjeta de red) para poder escuchar requests y responder, acceder al file system del sistema operativo que estemos usando, para leer archivos (ej: un documento HTML) y enviarlos, poder levantar y correr un server, comunicarnos con una base de datos, desarrollar una API, etc, no son capacidades propias de un lenguaje como JavaScript.
Es por esto que necesitamos un intermediario, algo así como una API que nos permita, escribiendo el código JavaScript que ya conocemos, acceder a este tipo de funcionalidades, necesarias para el backend de nuestra aplicación.
Vamos a llamar entorno de ejecución a todo lo que necesitamos para poder ejecutar nuestro código e interactuar con estas funcionalidades. Node es quien nos va a dar este entorno y nos va a permitir ejecutar código JavaScript prácticamente en todos lados.
Uno de los componentes de Node es V8, un engine de JavaScript que se encarga de parsear, compilar, optimizar, interpretar y ejecutar nuestro código.
La computadora no entiende (y por lo tanto no puede ejecutar) JavaScript directamente. Un engine, como lo es V8, toma nuestro código JavaScript y lo convierte a algo que si entiende, lo que se conoce como código máquina o binario.
Aparte de V8, tenemos el lenguaje C++, el cual, a traves de ciertas librerias que vienen con Node (como libuv), nos permiten acceder a funcionalidad de más bajo nivel como las mencionadas antes e interactucar directamente con el sistema operativo. Para acceder a esa funcionalidad, vamos a utilizar la API que nos provee Node.
Las operaciones de I/O son aquellas que se producen cuando hay una comunicación entre una computadora y ciertos periféricos, como pueden ser una tarjeta de red ó un disco.
Estas operaciones pueden consistir en, por ejemplo, intercambiar información a través de una red (networking) realizando requests, escuchar requests en cierto puerto y generar una respuesta, acceder a una base de datos ó realizar diversas acciones con el filesystem, como crear, leer y escribir archivos, copiar y pegar archivos, creary eliminar directorios, etc.
Las aplicaciones desarrolladas en Node corren en un único proceso (aka thread). Es decir, no crean un proceso nuevo por cada request. Node nos provee de muchos métodos de I/O asincrónicos a través de su API que previenen que el código se bloquee. La mayoría de las librerías, módulos y frameworks de Node estan escritas utilizando el paradigma asincrónico, por lo que encontrar código bloqueante o sincrónico es más bien la excepción y no la norma.
Cuando Node realiza alguna operación de I/O, en lugar de bloquear el único thread del que disponemos y desperdiciar ciclos de CPU esperando, Node retomará las operaciones pendientes cuando la respuesta se encuentre disponible.
Esto le permite a Node por ejemplo,poder manejar tranquilamente miles de conexiones concurrentes con un único servidor levantado (recordemos que operamos con un único proceso), lo cual nos proporciona un gran ahorro de recursos, hardware, etc y evitamos tener que lidiar con el manejo de threads para la concurrencia, simplificando en gran medida y agilizando el desarrollo.
Por lo tanto, podríamos decir que Node termina siendo un entorno de ejecución para poder correr código JavaScript y una API (ó librería) que nos provee acceso a funcionalidades necesarias para el backend de nuestra aplicación.
Gracias a esta API, Node nos permite construir desde pequeñas aplicaciones de línea de comandos (CLI), hasta servidores HTTP para crear sitios dinámicos y aplicaciones web.
REPL es una sigla que viene de Read, Eval, Print, Loop. Nos permite ejecutar Node en la terminal para probar cosas, como si se tratase de la consola del browser.
// 1
console.log("Hello World");
// 2
node helloworld.js
// sync version
const result = database.query("SELECT * FROM veryHugeTable");
console.log("Hello World");
// async version
database.query("SELECT * FROM hugetable", function(rows) {
const result = rows;
});
console.log("Hello World");
Para leer argumentos a través de la terminal/CLI, podemos utilizar process.argv
, que nos da acceso a un Array. Notar que este array también incluye como argumentos el comando que usamos para correr nuestro script y la ruta del archivo, por lo que los argumentos que nos interesan comienzan recién a partir del índice 2 del mismo, los cuales podemos obtener haciendo
const args = process.argv.slice(2);
Para más info, ver Node.js, accept arguments from the command line
process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
})
Podemos usar Node para leer y escribir archivos, a través del módulo fs
- Crear un archivo
readMe.txt
(en el mismo directorio donde tengamos nuestroindex.js
) con el contenido de este txt
const fs = require('fs');
const { readFileSync } = fs;
const txt = readFileSync('readMe.txt', 'utf-8');
console.log(txt);
const fs = require('fs');
const { readFileSync } = fs;
const readMe = readFileSync('readMe.txt', 'utf-8');
writeFileSync('writeMe.txt', readMe);
console.log(txt);
const fs = require('fs');
const { readFile } = fs;
readFile('readMe.txt', 'utf8', (err, data) => {
if (err) {
throw err
};
console.log(data)
});
// const readFile = util.promisify(fs.readFile)
// readFile("path/to/myfile").then(file => console.log(file))
const fs = require('fs');
const { readFile: read, writeFile: write } = fs;
read('readMe.txt', 'utf-8', (err, data) => {
if (err) {
console.error(err);
}
write('writeMe.txt', data, err => console.error(err));
})
console.log('HELLO!');
unlink
va a retornar un error
const fs = require('fs');
const { unlink: del } = fs;
const filePath = 'writeMe.txt';
del(filePath, err => {
if (err) {
throw err;
}
console.log(`${filePath} was deleted succesfully.`)
});
Usar fs.copyFile
para copiar 'readMe.txt'
a 'readMeCopy.txt'
Usar fs.rename
para renombrar 'readMeCopy.txt'
a 'readMe_copy.txt'
const fs = require('fs');
const { mkdir, rmdir, readFile: read, writeFile: write } = fs;
mkdir('node-fs', err => {
if (err)
read('readMe.txt', 'utf-8', (err, data) => {
write('./node-fs/writeMe.txt', data)
})
})
const fs = require('fs');
const { mkdir, rename } = fs;
function move(src, dst) {
rename(src, dst, err => {
if (err) {
throw err;
}
});
}
mkdir('node-fs', err => {
if (err) {
throw err;
}
move('readMe_copy.txt', 'node-fs/readMe_copy.txt');
});
const fs = require('fs');
const { rmdir } = fs;
rmdir('node-fs');
const fs = require('fs');
const { rmdir, unlink: del } = fs;
del('./node-fs/writeMe.txt', err => {
if (err) {
throw err;
}
rmdir('node-fs');
})
- Usar
prepend-file
para agregar el texto
`Con 15 peso', con 15 peso' me hago
`
al principio del texto del archivo y mostrar el resultado en la consola
-
Escribir en el archivo
ticket.txt
el textoGastaste ${importe} en ${producto}!
, dondeimporte
yproducto
son parámetros que se reciben por consola -
Escribir la función
ls
que tome como parámetro por consola un string que represente la ruta de un directorio local y loguee en consola los archivos del directorio. Investigar para esto el métodofs.readdir
El módulo path
de Node es muy útil para trabajar con y manipular rutas (paths) de diferentes maneras.
const path = require("path");
// Normalizar una ruta
console.log(path.normalize("/test/test1//2slashes/1slash/tab/..")); // /test/test1/2slashes/1slash
// Unir múltiples paths
console.log(path.join("/first", "second", "something", "then", "..")); // /first/second/something
// Resolver un path (obtener la ruta absoluta de un archivo)
console.log(path.resolve("first.js"));
// Obtener la extensión de un archivo
console.log(path.extname("main.js")); // .js
El módulo http
nos provee de la funcionalidad necesaria para crear servidores HTTP y realizar requests.
// server v1
const http = require("http");
// `createServer()` crea un nuevo servidor HTTP y retorna un objeto, que tiene el método `listen`
const server = http.createServer();
server.listen(8888);
- Abrir
http://localhost:8888/
en el browser
node server.js
- Cuando el servidor recibe un request, se dispara el evento
request
y se ejecuta el callback que recibecreateServer
. Este callback tiene 2 parámetros, los objetosrequest
yresponse
// server v2
// en un archivo server.js
const http = require("http");
http.createServer((request, response) => {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Hello World');
response.end();
}).listen(8888);
node server.js
// 7. server v3 (refactoring)
const http = require("http");
const HOSTNAME = '127.0.0.1'
const PORT = 8888;
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(PORT, error => {
if (error) {
console.error('THIS IS FINE. 🔥🔥🔥🚒');
} else {
console.log(`Server listening on http://${HOSTNAME}:${PORT}`);
}
});
// server v4 (ejemplo de la documentación de Node)
const http = require('http');
const HOSTNAME = '127.0.0.1';
const PORT = process.env.PORT;
const server = http.createServer((request, response) => {
response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.end('Hello World!');
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${HOSTNAME}:${PORT}/`);
})
PORT=8888 node app.js
Ver Making HTTP requests with Node
Ver node-fetch
Tip: Usar nodemon
y crear el script dev: nodemon index.js
en el package.json
para correrlos
- Crear un servidor en Node, que escuche en el puerto
8001
(el puerto debe pasarse como parámetro a través de las variables de entorno) y responda con el siguiente HTML:
<h1>Hey!</h1>
<p>Soy un servidor <code>Node</code> y vos estás haciendo un <code>{{METHOD}}</code> a la <em>url</em> <code>{{URL}}</code> 🎉</p>
donde {{URL}}
es la url a la cual el cliente hizo el request, ej: localhost:8001/node
y {{METHOD}}
es el verbo HTTP utilizado, ej: GET
. Para visualizar correctamente el HTML, tendremos que agregar el charset
al Content-Type
en los headers:
'Content-Type': 'text/html; charset=utf-8'
- Modificar el código del ejercicio anterior, para que si se hace un request a
/hello
, el servidor responda con el siguiente HTML:
<h1>Hola! 😃</h1>
<p>Soy un servidor <code>Node</code> y vos estás haciendo un <code>{{METHOD}}</code> a la <em>url</em> <code>{{URL}}</code> 🎉</p>
y si el request se hace a /bye
, la respuesta sea
<h1>Bueno. 😢</h1>
<p>Soy un servidor <code>Node</code> y vos estás haciendo un <code>{{METHOD}}</code> a la <em>url</em> <code>{{URL}}</code> 🎉</p>
Nota: en ambos casos, lo único que cambia en la respuesta es el contenido del h1
y si el request se hace a /
, la respuesta debe ser la misma del primer ejercicio.
-
Modificar el código del ejercicio anterior, para que, si se realiza un request a la url
/christmas
, el servidor devuelva, en formatoJSON
, la cantidad de minutos que faltan entre la fecha actual y el 25 de Diciembre, 0hs. Usar date-fns para realizar este cálculo. (Nota: para importar el módulo correspondiente, usarconst differenceInMinutes = require('date-fns/differenceInMinutes')
, en la documentación está mal) -
Modificar el código del ejercicio 1, para tener el servidor en un archivo
server.js
, que exporte la funciónup
, la cual sirve para iniciar el servidor en el puerto8888
. Esta función debe loguear mensajes por consola indicando cuando el servidor está levantado y cuando recibe un nuevo request. El callback que recibecreateServer
debe modularizarse y moverse a la funciónonRequest
. Por último, Crear el archivoindex.js
, en el cual vamos a importar el server y utilizar la funciónup
para correrlo. -
Crear un archivo
index.html
con el siguiente contenido
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Server</title>
<link href="https://fonts.googleapis.com/css?family=Molle:400i&display=swap" rel="stylesheet" />
<style>
body {
font-family: 'Molle', cursive;
color: #026e00;
padding: 24px;
}
h1 {
font-size: 5em;
}
p {
font-size: 2em;
}
h1,
p {
text-align: center;
}
</style>
</head>
<body>
<h1>Hey!</h1>
<p>Soy un servidor <code>Node</code> y estás viendo el <code>index.html</code> que leí y te estoy mandando 🎉</p>
</body>
</html>
Luego, modificar el código del ítem anterior, para que como respuesta envíe el resultado de leer el contenido del archivo HTML creado.
- Modificar el
html
del ejercicio anterior para incluir esta imagen con el nombrenode.png
en el mismo. La imagen debe estar en el proyecto, dentro de una carpetaassets
. En el caso de que el servidor reciba un request a una ruta no definida, debe responder con un status404
y el html<h2>404 - Page not found :(</h2>
Los módulos forman parte de los bloques fundamentales que utilizamos en Node para construir aplicaciones. Como buena práctica, vamos a tratar siempre en lo posible de construir módulos pequeños, con un propósito único y claro.
Los módulos en Node se importan utilizando la función require()
. Para ser cargado como módulo, un paquete debe contener un archivo index.js
ó el campo main
definido en el package.json
, para indicar un entry point específico.
require
, por lo que siempre debemos cargarlos al inicio del archivo si no queremos bloquear la aplicación
Un archivo js importado con require()
es un módulo pero no un paquete, porque no tiene un archivo package.json
.
Un paquete es una carpeta/directorio que contiene lo siguiente:
- Un archivo
package.json
, que describe la aplicación y sus dependencias - Un entry point definido, que por default es el archivo
index.js
- Un subdirectorio
node_modules
, que es la ubicación default donde Node va a buscar las dependencias del proyecto - El resto de los archivos que forman parte del código fuente de la aplicación
// In `greetings.js`, create three functions
const sayHello = name => `Hello, ${name}`;
const flatter = () => `Look how gorgeous you are today!`;
const sayGoodbye = name => `Goodbye, ${name}`;
// Export two of them
module.exports = {
sayHello,
flatter
};
// Load the module "greetings.js"
const greetings = require("./greetings.js");
// Use exported functions
console.log(greetings.sayHello("Baptiste")); // "Hello, Baptiste"
console.log(greetings.flatter()); // "Look how gorgeous you are today!"
- Ver más ejemplos en The HTTP Module - Eloquent JavaScript
- Crear
package.json
usando el comandonpm init
⚠️ Agregarnode_modules
al.gitignore
⚠️ Los archivospackage.json
ypackage-lock.json
deben comitearse SIEMPRE!
Instalar módulo como dependencia de nuestro proyecto
# son equivalentes
npm install <MODULE_NAME>
npm i <MODULE_NAME>
Instalar módulo como dependencia de desarrollo de nuestro proyecto
# son equivalentes
npm install --save-dev <MODULE_NAME>
npm i --save-dev <MODULE_NAME>
Instalar módulo de forma global
# son equivalentes
npm install --global <MODULE_NAME>
npm i -g <MODULE_NAME>
Actualizar módulo a la siguiente versión disponible, según cómo tengamos declarada la dependencia en el package.json
(ver SEMVER)
npm update <MODULE_NAME>
Desinstalar una dependencia del proyecto
npm uninstall <MODULE_NAME>
Desinstalar un módulo global
npm uninstall -g <MODULE_NAME>
NPX sirve para ejecutar ciertos comandos (fundamentalmente relacionados a CLIs) sin tener la necesidad de descargar e instalar un módulo en nuestro proyecto