JS, Node.js, Frontend, Backend, Firebase, Express, Patrones, HTML5_APIs, Asincronía, Websockets, Testing
Influencias / usos
-
Otros frameworks similares:
- Zend (PHP)
- Django (Python)
- Sinatra (Ruby)
-
Uso:
- API JSON
- Single Pages
- App tiempo real
Pros
- Rutas
- Parámetros
- Formularios y subida de ficheros
- Cookies
- Sesiones
- Templates
Contras
- Base de datos / ORM
- Autenticación de usuarios
- Seguridad
- Migraciones
- Deployment
- Organización del código
Documentación
- Web Oficial
- Como empezar
- Guía
- API de 4.x
- Temas Avanzados
- Recursos
- Preguntas más frecuentes
Recursos
- Going out to eat and understanding the basics of Express.js
- Introduction to Node & Express
- Build a Weather Website in 30 minutes with Node.js + Express + OpenWeather
- Express.js and AWS Lambda — a serverless love story
- Simple server side cache for Express.js with Node.js
- Using Node.js & Express.js to save data to MongoDB Database
- Getting off the ground with Express.js
- Express.js on Cloud Functions for Firebase
- [ Expressjs ] Cracking nuts, put the IP Address to BlackList
- [ Expressjs ] Cracking nuts, override res.send
- [Express.js] Measuring performance of HTTP Request
- How to Do Layouts the Right Way in Pug and Express.js
- Using Async Await in Express with Node 9
- Building a scalable Node.js Express app
- A crash course on Serverless APIs with Express and MongoDB
- Building a Node.js REST API with Express
- Creating RESTful API with Node.js: Hapi vs. Express
- Beautiful Node: Building an API endpoint with Express, Mongoose, Validation and Promises
- Sessionless Authentication using JWTs (with Node + Express + Passport JS)
- CRUD in React and Express (MySQL)
- Building a Simple CRUD Application with Express and MongoDB
- Docker development workflow: Node, Express, Mongo
- Using Let’s Encrypt with Express
- From Express.js to AWS Lambda: Migrating existing Node.js applications to serverless
- Server & Authentication Basics: Express, Sessions, Passport, and cURL
- THE BEGINNER’S GUIDE: Understanding Node.js & Express.js fundamentals
- Building a Node.js Powered API with Express, Mongoose & MongoDB
- Creating node api’s became a lot easier, introduction to build-express-api
- Geeky Theory | Introducción a Express.js
- How express.js works - Understanding the internals of the express library ⚙️
- How I added awesome multi-threaded features to Express JS
- Build a Complete MVC Website With ExpressJS
De Express 4.x a Express 5.x (hoy es Alpha)
http/s: peticion, respuesta, codigo de estado y verbos...
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ href │
├──────────┬──┬─────────────────────┬─────────────────────┬───────────────────────────┬───────┤
│ protocol │ │ auth │ host │ path │ hash │
│ │ │ ├──────────────┬──────┼──────────┬────────────────┤ │
│ │ │ │ hostname │ port │ pathname │ search │ │
│ │ │ │ │ │ ├─┬──────────────┤ │
│ │ │ │ │ │ │ │ query │ │
" https: // user : pass @ sub.host.com : 8080 /p/a/t/h ? query=string #hash "
│ │ │ │ │ hostname │ port │ │ │ │
│ │ │ │ ├──────────────┴──────┤ │ │ │
│ protocol │ │ username │ password │ host │ │ │ │
├──────────┴──┼──────────┴──────────┼─────────────────────┤ │ │ │
│ origin │ │ origin │ pathname │ search │ hash │
├─────────────┴─────────────────────┴─────────────────────┴──────────┴────────────────┴───────┤
│ href │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
(all spaces in the "" line should be ignored — they are purely for formatting)
-
Instalación Global:
npm install -g express
-
Instalación versiones anteriores:
npm install -g [email protected]
var express = require('express');
var app = express();
// C9
var puerto = process.env.PORT || 3000;
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(puerto, function () {
console.log('Example app listening on port ' + puerto);
});
El generador de express sigue un esquema www/var
que no se corresponde con proyectos reales.
Solo tienes que seguir estos pasos para que arranque al estilo Express 3.x
Instalación global del generador
npm install express-generator -g
Opciones de instalación
express -h
Generar un proyecto
express <nombre_proyecto>
Entramos en la carpeta e instalamos las dependencias
cd <nombre_proyecto> && npm install
Estructura de un Proyecto (MVC)
├── app.js (Nuestra aplicación - módulo)
├── bin (Gestión de la aplicación)
│ └── www
├── package.json (Información y dependencias)
├── public (Nuestros estáticos)
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes (Nuestros controladores)
│ ├── index.js
│ └── users.js
└── views (Nuestras vistas/plantillas)
├── error.jade
├── index.jade
└── layout.jade
Ejecutando la aplicación:
- Windows
set DEBUG=<nombre_proyecto>:* & npm start
- MacOS/Linux
DEBUG=<nombre_proyecto>:* npm start
Mecánica: app.set()
- Nos permite establecer la configuración de Express
- Podemos almacenar datos personalizados de manera global
Guardando la versión
app.set('version', '1.5.0');
app.get('version'); // 1.5.0
Maneras de Habilitar contenido
app.enable('dia_soleado'); // igual a -> app.set('dia_soleado', true);
app.enabled('dia_soleado'); // igual a -> app.get('dia_soleado') === true;
app.disabled('dia_soleado'); // igual a -> app.get('dia_soleado') === false;
Definiendo el puerto
app.set('port', process.env.PORT || 3000);
Configuraciones según el entorno
- Para todos los entornos
NODE_ENV=production node app.js
app.configure(() => {
app.set('estado_aplicacion', '*');
});
- Solo desarrollo
NODE_ENV=development node app.js
app.configure('development', () => {
app.set('estado_aplicacion', 'development');
});
- Solo producción
NODE_ENV=production node app.js
app.configure('production', () => {
app.set('estado_aplicacion', 'production');
});
- Solo personalizado
NODE_ENV=personalizado1 node app.js
app.configure('personalizado1', () => {
app.set('estado_aplicacion', 'personalizado1');
});
Motores de Plantillas
- Variables locales (solo disponibles para las plantillas)
// Guardando
app.locals.title = 'My App';
app.locals.email = '[email protected]';
// Usando
app.locals.title // My App
app.locals.email // [email protected]
- Definiendo el sistema de plantillas que usaremos
// npm install jade --save
const express = require('express'),
jade = require('jade'),
app = express();
app.set('view engine', 'jade');
app.get('/', (req, res) => {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
Comparativa de Motores de plantillas
app.all(), app.get(), app.post(), app.put(), app.delete(), app.route(), etc...
app.METODO(Ruta, Manejador)
Estructura
- app Instanciado de express
- METODO Metodo HTTP de la Ruta
- Soportados: get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search y connect.
- Para todas las rutas usamos app.all()
- Ruta Ruta (url) donde se aplica
- Podemos usar
- Series
- Patrones de Series (Subtipo de Regex), reducido a los subconjuntos ?, +, *, y ()
- Expresiones regulares
- Podemos usar
- Manejador La función que será llamada cuando se alcance la ruta con el método/s correctos/s
- Se puede usar funciones individuales
- Se pueden hacer matrices de funciones
- Se pueden mezclar matrices y funciones individuales
- Argumentos:
- Obj Request de Node.js
- Obj Response de Node.js
- next() Función que dispara el siguiente middleware
delimitando a un único método
app.get('/', (req, res, next) => {
res.send('Solo get como método me vale...');
});
Otra forma de delimitar a un método
app['m-search']('/', (req, res, next) => {
res.send('Solo m-search como método me vale...');
});
Permitiendo todos los métodos
app.all('/', (req, res, next) => {
res.send('Cualquier método me vale...');
});
Raiz http://localhost:8080/
app.get('/', (req, res, next) => {
res.send('Esto es la Raiz');
});
Básicas http://localhost:8080/hola
app.get('/hola', (req, res, next) => {
res.send('Esto es /hola');
});
Capturando Parámetros http://localhost:8080/hola/Eduardo
, http://localhost:8080/hola/Oscar
...
app.get('/hello/:nombre', (req, res) => {
res.send(`Hola, ${req.params.nombre}!`);
});
Capturando varios parámetros http://localhost:8080/desde/Madrid/a/Malga
, http://localhost:8080/desde/Madrid/a/NYC
...
app.get('/desde/:origen/a/:destino', (req, res, next) => {
res.send(`Quieres ir de ${req.params.origen} a ${req.params.destino}`);
});
Capturando varios parámetros y alguno determiando http://localhost:8080/mensaje/1/editar
, http://localhost:8080/mensaje/500/borrar
...
app.get('/mensaje/:id/:accion(editar|borrar)', (req, res, next) => {
res.send(`Quieres ${req.params.accion} el mensaje numero ${req.params.id}`);
});
Parámetros opcionales http://localhost:8080/user/1/editar
, http://localhost:8080/user/500/borrar
...
app.get('/user/:id/:comando?', (req, res, next) => {
if(req.params.comando){
res.send(`Tenemos algo! Quieres ${req.params.comando}`);
} else {
res.send("Nada de nada...");
}
});
Más parámetros opcionales http://localhost:8080/user/1.pdf
, http://localhost:8080/user/500.zip
, etc...
app.get('/user/:id.:formato?', (req, res, next) => {
if(req.params.formato){
res.send(`[${req.params.formato}] Extensión requerida... `);
} else {
res.send("Sin Extensión requerida");
}
});
Tipo fichero http://localhost:8080/hola.text
app.get('/hola.text', (req, res) => {
res.send('Hola');
});
Patrones de serie (?
) http://localhost:8080/acd
o http://localhost:8080/abcd
app.get('/ab?cd', (req, res) => {
res.send('ab?cd');
});
Patrones de serie (+
) http://localhost:8080/abcd
, http://localhost:8080/abbbbbcd
, etc...
app.get('/ab+cd', (req, res) => {
res.send('ab+cd');
});
Patrones de serie (*
) http://localhost:8080/abcd
, http://localhost:8080/abAALGOOOcd
, etc...
app.get('/ab*cd', (req, res) => {
res.send('ab*cd');
});
Patrones de serie (()
) http://localhost:8080/abe
o http://localhost:8080/abcd
app.get('/a(bc)d', (req, res) => {
res.send('a(bc)d');
});
Expresiones regulares http://localhost:8080/mcfly
, http://localhost:8080/dragonfly
, etc...
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});
Función individual
app.get('/example/a', (req, res) => {
res.send('Hola desde A!');
});
Manejadores: Dos funciones individuales
app.get('/example/b', (req, res, next) => {
console.log('La respuesta se enviará a la siguiente función...');
next();
}, (req, res) => {
res.send('Hola desde B!');
});
Manejadores: Matrices
const cb0 = (req, res, next) => {
console.log('CB0');
next();
};
const cb1 = (req, res, next) => {
console.log('CB1');
next();
};
const cb2 = (req, res) => {
res.send('Hola desde C!');
};
app.get('/example/c', [cb0, cb1, cb2]);
Manejadores: Matrices y funciones individuales
const cb0 = (req, res, next) => {
console.log('CB0');
next();
};
const cb1 = (req, res, next) => {
console.log('CB1');
next();
};
app.get('/example/d', [cb0, cb1], (req, res, next) => {
console.log('La respuesta se enviará a la siguiente función...');
next();
}, (req, res) => {
res.send('Hola desde D!');
});
Destacado
req.ip
Almacena la IP desde donde se realizó la peticioónreq.is
Que tipo de datos nos llegan desde la petición. Booleanoreq.params
Contenido de la ruta (http://localhost:8080/usuarios/:id)req.query
Contenido de la query (consulta) de la URLreq.body
Contenido dentro de la propia petición- Y muchos más...
Ejemplo de req.is
req.is('json');
req.is('application/*');
req.is('application/json');
Ejemplo de req.query
// http://localhost:8080/peliculas?categoria=Ficcion&director=George+Lucas
app.get('/peliculas', (req, res, next) => {
console.log(req.query.director) // George Lucas
console.log(req.query.categoria) // Ficcion
});
// http://localhost:8080/peliculas?categoria[tipo]=Corto&director=Yo+Mismo
app.get('/peliculas', (req, res, next) => {
console.log(req.query.director) // Yo Mismo
console.log(req.query.categoria.tipo) // Corto
});
Destacado
res.download()
: Solicita un archivo para descargarlo.res.end()
: Finaliza el proceso de respuesta.res.json()
: Envía una respuesta JSON.res.jsonp()
: Envía una respuesta JSON con soporte JSONP.- Valores por defecto ajustables en
app.set()
?callback=
valor por defecto en la peticiónres.jsonp({date: newDate()});
- Valores por defecto ajustables en
res.redirect()
: Redirecciona una solicitud.res.render()
: Renderiza una plantilla a la que le pasa además datos (opcional)res.send()
: Envía una respuesta de varios tipos.- Muy flexible
- Código y contenido
res.send(404,'Oops...');
- Enviar un JSON
res.send({mensaje: "secreto"});
- Solo código (autocompleta el mensaje)
res.send(200);
res.sendFile()
: Envía un archivo para ser visualizado.res.sendStatus()
: Envia un estado HTTP.- Y mucho más...
Middleware: La clave
Patrón del middelware
- Todas las funciones reciben tres parámetros
request
,response
ynext
- Es una secuencia de callbacks que se autogestionan
- Si todo sale correctamente, nuestra función debe invocar a la siguiente usando
next()
- Si algo va mal, debe gestionar la salida con
response
. - Puede resultar complejo saber cuantas funciones quedan o han pasado.
- Es un esquema de cascada y delegación de resposabildiad muy claro.
Tipos de Middelware
- Middleware de nivel de aplicación
- Middleware de nivel de direccionador
- Middleware de manejo de errores
- Middleware incorporado
- Middleware de terceros
Esto funciona nivel de toda la aplicación
//import express from 'express';
const express = require('express');
const app = express();
function chivato (req, res, next) {
console.log(`Nueva petición en ${req.url} con el método${req.method}`);
next();
}
app.use(chivato);
app.get('/', (req, res) => {
res.send('Hola a todos!');
});
app.listen(3000);
Middleware Global, a nivel de toda la aplicación
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
Middelware de ruta, en este caso /user/:id
const app = express();
app.get('/user/:id', (req, res, next) => {
console.log('ID:', req.params.id);
next();
}, (req, res, next) => {
res.send('User Info');
});
Hacemos uso de un argumento adiccional, err
//import bodyParser from 'body-parser';
//import methodOverride from 'method-override';
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
app.use(gestionErrorServer);
function gestionErrorServer(err, req, res, next) {
//Soporte para llamdas AJAX (xhr)
if (req.xhr) {
res.status(500).send({ error: 'Something failed!' });
} else {
res.status(500);
res.render('error', { error: err });
}
}
//...
Importante
- Desde la versión 4.x Express no depende de Connect
- Todo el middelware que habia en Express 3.x, es sacado del core de express 4.x
- Solamente queda incorporado express.static
Incluyendo archivos estáticos
app.use(express.static('public'));
Configurando la carpeta pública
const options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders(res, path, stat) {
res.set('x-timestamp', Date.now());
}
};
app.use(express.static('public', options));
Usando múltiples directorios estáticos
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));
Habilitando CORS
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Baneando Navegadores (IE8 y anteriores)
// banear.js
const banned = [ 'MSIE', 'Trident/6.0'];
export default () => (req, res, next) => {
if (req.headers['user-agent'] !== undefined && req.headers['user-agent'].includes(banned)) {
res.end('Navegador no compatible');
} else {
next();
}
};
Redireccionando al usuario en caso de no estar logeados
export default (req, res) => {
if (req.params.usuario.logged){
next();
} else {
res.redirect('/login');
}
};
Gestion de errores tipo 4xx y tipo 5xx
// Error 404
app.use((req, res) => {
res.status(404);
res.render('404', { title: '404 - No encontrado' });
});
// Error 500 (solo en caso de desarrollo)
app.use('development', (error, req, res, next) => {
res.status(500);
res.render('500', {
title: 'Panico en la sala!!... Tenemos un 500... o.O',
error
});
});
// Error 500 (solo en caso producción)
app.use('production', (error, req, res, next) => {
res.status(500);
res.render('500', {
title: 'Oops… ¡Algo salió mal!'
});
// Mandamos un email con los datos al equipo.
});
Molularidad con app.route()
app.route('/pelicula')
.get((req, res) => {
res.send('todas las peliculas...');
})
.post((req, res) => {
res.send('Añadir una pelicula...');
})
Middleware de nivel Global
const app = express();
const router = express.Router();
router.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
Middelware a nivel de ruta, en este caso /user/:id
const app = express();
const router = express.Router();
router.get('/user/:id', (req, res, next) => {
console.log('ID:', req.params.id);
next();
}, (req, res, next) => {
res.send('User Info');
});
1 - Crea tu primer script de Express
Objetivos
- Crear un proxy para hacer llamadas AJAX
GET
que no tengan CORS Habilitado/cors?url=...
- Crear un sistema de estaticos para que
./public
sea accesible - poner como root (
/
) la ruta./public/index.html
- Lanzar todo con el comando
npm start
// npm install express request
var express = require('express');
var request = require('request');
var app = express();
var path = require('path');
// Static files
app.use(express.static('public'));
// viewed at http://localhost:8080
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/public/index.html'));
});
app.get('/proxy', function (req, res){
// @Example: localhost:8080/proxy?url=http://airemad.com/api/v1/station/
// CORS
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
// PIPE Response
request({
url: req.query.url,
method: "GET"
}).pipe(res)
});
app.listen(process.env.PORT || 8080);