Skip to content

Latest commit

 

History

History
514 lines (389 loc) · 16 KB

File metadata and controls

514 lines (389 loc) · 16 KB

shieldsIO shieldsIO shieldsIO

WideImg

Máster en Programación FullStack con JavaScript y Node.js

JS, Node.js, Frontend, Backend, Firebase, Express, Patrones, HTML5_APIs, Asincronía, Websockets, Testing

Clase 69

Express: ¿Cómo debo estructurar mi aplicación?

broma

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');
});
  

Controladores de estilo MVC

El ciclo de Petición/Respuesta en Express

Esquema

Express: Middelware Oficial (no incorporado) desde Express v4.x

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

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

  • No se peude 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);

Express: Middelware de la comunidad

Edificios

Destacados

Express: rendimiento y fiabilidad

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 y try... 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
  • 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

Express: Seguridad

Claves

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