JS, Node.js, Frontend, Backend, Firebase, Express, Patrones, HTML5_APIs, Asincronía, Websockets, Testing
Listas de rutas (Routes separation)
const express = require('express'),
logger = require('morgan'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
site = require('./site'),
post = require('./post'),
user = require('./user'),
app = express();
// Config
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(logger('dev'));
app.use(methodOverride('_method'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname + '/public'));
// General
app.get('/', site.index);
// User
app.get('/users', user.list);
app.all('/user/:id/:op?', user.load);
app.get('/user/:id', user.view);
app.get('/user/:id/view', user.view);
app.get('/user/:id/edit', user.edit);
app.put('/user/:id/edit', user.update);
// Posts
app.get('/posts', post.list);
app.listen(8080, () => {
console.log('Express started on port 8080');
});
Correlación de rutas (Routes Map)
const express = require('express'),
app = express();
app.map = (a, route) => {
route = route || '';
for (let key in a) {
switch (typeof a[key]) {
// { '/path': { ... }}
case 'object':
app.map(a[key], route + key);
break;
// get: function(){ ... }
case 'function':
console.log('%s %s', key, route);
app[key](route, a[key]);
break;
}
}
};
const users = {
list: (req, res) => {
res.send('user list');
},
get: (req, res) => {
res.send('user ' + req.params.uid);
},
delete: (req, res) => {
res.send('delete users');
}
};
const pets = {
list: (req, res) => {
res.send('user ' + req.params.uid + '\'s pets');
},
delete: (req, res) => {
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
app.map({
'/users': {
get: users.list,
delete: users.delete,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
delete: pets.delete
}
}
}
}
});
app.listen(8080, () => {
console.log('Express started on port 8080');
});
serve-favicon favicon serving middleware
const express = require('express'),
favicon = require('serve-favicon'),
path = require('path');
const app = express()
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
// Tu código
app.listen(8080)
Morgan Logger para peticiones HTTP
const express = require('express'),
fs = require('fs'),
morgan = require('morgan'),
path = require('path'),
app = express();
// Write Stream (modo append)
const accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' })
// setup the logger
app.use(morgan('combined', { stream: accessLogStream }))
app.get('/', (req, res) => {
res.send('hello, world!')
})
app.listen(8080)
body-parser Node.js body parsing middleware
const express = require('express'),
bodyParser = require('body-parser'),
app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(({body}, res) => {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(JSON.stringify(body, null, 2))
})
app.listen(8080);
csurf CSRF token middleware
- Se complementa con
cookie-parser
ya que es necesario gestionar cookies - Incluye soporte para Ajax
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const bodyParser = require('body-parser');
const express = require('express');
// setup route middlewares
const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });
// create express app
const app = express();
// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser())
app.get('/form', csrfProtection, (req, res) => {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, (req, res) => {
res.send('data is being processed')
})
app.listen(8080);
cors Node.js CORS middleware
const express = require('express'),
cors = require('cors'),
app = express();
/* Solo en caso de quererlo en toda la aplicación
app.use(cors())
*/
app.get('/products/:id', cors(), (req, res, next) => {
res.json({msg: 'This is CORS-enabled for a Single Route'})
})
app.listen(8080, () => {
console.log('CORS-enabled web server listening on port 80')
})
compression Node.js compression middleware
- Soporta gzip
- Soporta deflate
- Tutorial - compression
const compression = require('compression'),
express = require('express');
const app = express();
// compress all responses
app.use(compression())
// More code...
app.listen(8080, () => {
console.log('CORS-enabled web server listening on port 80')
})
express-session Simple session middleware for Express
const express = require('express'),
parseurl = require('parseurl'),
session = require('express-session');
const app = express();
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use((req, res, next) => {
if (!req.session.views) {
req.session.views = {}
}
// get the url pathname
const pathname = parseurl(req).pathname;
// count the views
req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
next()
})
app.get('/foo', (req, res, next) => {
res.send(`you viewed this page ${req.session.views['/foo']} times`)
})
app.get('/bar', (req, res, next) => {
res.send(`you viewed this page ${req.session.views['/bar']} times`)
})
app.listen(8080, () => {
console.log('CORS-enabled web server listening on port 80')
})
multer Node.js middleware for handling multipart/form-data
- Se utiliza principalmente para la gestión de ficheros
- Internamente utiliza busboy
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const app = express();
app.post('/profile', upload.single('avatar'), (req, res, next) => {
/*
req.file is the `avatar` file
req.body will hold the text fields, if there were any
*/
})
app.post('/photos/upload', upload.array('photos', 12), (req, res, next) => {
/*
req.files is array of `photos` files
req.body will contain the text fields, if there were any
*/
})
const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]);
app.post('/cool-profile', cpUpload, (req, res, next) => {
/*
req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
e.g.
req.files['avatar'][0] -> File
req.files['gallery'] -> Array
req.body will contain the text fields, if there were any
*/
})
app.listen(8080, () => {
console.log('CORS-enabled web server listening on port 80')
})
cookie-session Simple cookie-based session middleware
- Tutorial - cookie-session
- No se puede exceder el maximo de cookies y contenido que permite el browser
- No requiere almacenamiento en base de datos
- Inicializa y parsea los datos de sesión del usuario
- Utilizando cookies como almacenamiento
- Tiene algunas opciones avanzadas
const cookieSession = require('cookie-session'),
express = require('express'),
app = express();
// trust first proxy
app.set('trust proxy', 1)
app.use(cookieSession({
name: 'session',
keys: ['key1', 'key2']
}))
// Simple view counter
app.get('/', ({session}, res, next) => {
// Update views
session.views = (session.views || 0) + 1
// Write response
res.end(`${session.views} views`)
})
app.listen(8080)
cookie-parser: Parse HTTP request cookies
const express = require('express'),
cookieParser = require('cookie-parser'),
app = express();
app.use(cookieParser())
app.get('/', ({cookies, signedCookies}, res) => {
// Cookies that have not been signed
console.log('Cookies: ', cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', signedCookies)
})
app.listen(8080)
// curl command that sends an HTTP request with two cookies
// curl http://127.0.0.1:8080 --cookie "Cho=Kim;Greet=Hello"
serve-static Serve static files
const express = require('express'),
serveStatic = require('serve-static'),
app = express();
app.use(serveStatic('public/ftp', {'index': ['default.html', 'default.htm']}))
app.listen(8080)
vhost Virtual Domain Hosting
const express = require("express");
const vhost = require("vhost");
const app = express();
app.use(vhost("api.example.com", require("./apps/api/app")));
app.use(vhost("www.example.com", require("./apps/www/app")));
app.use(vhost('*.*.example.com', function handle (req, res, next) {
// for match of "foo.bar.example.com:8080" against "*.*.example.com":
console.dir(req.vhost.host) // => 'foo.bar.example.com:8080'
console.dir(req.vhost.hostname) // => 'foo.bar.example.com'
console.dir(req.vhost.length) // => 2
console.dir(req.vhost[0]) // => 'foo'
console.dir(req.vhost[1]) // => 'bar'
}))
app.listen(8080);
Destacados
- scheduled Scheduled job manager for JavaScript, linux cron pattern.
- Passport Simple, unobtrusive authentication for Node.js
- Helmet Help secure Express apps with various HTTP headers
- express-resource Resourceful routing for Express
- apollo-server-express 🌍 GraphQL server for Express, Connect, Hapi, Koa and more
- express-rate-limit Basic rate-limiting middleware for express
- express-fileupload Simple express file upload middleware that wraps around busboy
- express-status-monitor 🚀 Realtime Monitoring solution for Node.js/Express.js apps, inspired by status.github.com
- express-useragent NodeJS user-agent middleware
- grant OAuth Middleware for Express, Koa and Hapi
- Muchos más...
Claves: Lo esencial
- Utilizar la compresión de gzip
- No utilizar funciones síncronas
- Utilizar el middleware para el servicio de archivos estáticos. Evitar
res.sendFile()
y utilizar en su lugar serve-static - Realizar un registro correcto. Evitar
console
y utilizar en su lugar un logger - Manejar las excepciones correctamente usando
Promise
ytry... catch
- Asegurarse de que la aplicación se reinicia automáticamente
Claves: Entorno/Configuración
- Establecer NODE_ENV en production.
NODE_ENV=production node myapp.js
puede llegar a ser 3x más rápido Tutorial NODE_ENV - Asegurarse de que la aplicación se reinicia automáticamente con PM2
- Ejecutar la aplicación en un clúster con PM2 y gestionando el cache con Redis
- Almacenar en la caché los resultados de la solicitud con varnish o Nginx
- Utilizar un balanceador de carga
- Utilizar un proxy inverso como Nginx
Recursos
Claves
- No utilizar versiones en desuso o vulnerables de Express
- Utilizar TLS
- Utilizar Helmet
- Como mínimo, inhabilitar la cabecera
X-Powered-By
.app.disable('x-powered-by');
- Utilizar cookies de forma segura
- Utiliza cookie-session y express-session
- No utilizar el nombre de cookie de sesión predeterminado
- Asegurarse de que las dependencias sean seguras utiliza Node Security Platform y Synk
Genéricas
- Implementa el límite de velocidad para evitar ataques de fuerza bruta contra la autenticación con express-limiter
- Implementa protecciones CSRF con csurf
- Filtra y sanea siempre la entrada de datos del usuario para evitar ataques XSS
- No pierdas de vista el OWASP Top 10
- Evita ataques de inyección de SQL utilizando consultas parametrizadas o sentencias preparadas.
- Usa sqlmap para detectar vulnerabilidades de inyección de SQL en la aplicación.
- Usa nmap y sslyze para probar la configuración de los cifrados SSL, las claves y la renegociación, así como la validez del certificado.
- Usa safe-regex para evitar ataques de denegación de servicio de expresiones regulares.
Recursos