Cómo escribir el middleware Express.js

¿Alguna vez se ha preguntado qué está pasando dentro de todo el middleware Express.js que está agregando a su aplicación web? En realidad, es bastante impresionante el tipo de func...

Introducción

¿Alguna vez se ha preguntado qué está pasando dentro de todo el middleware Express.js que está agregando a su aplicación web? En realidad, es bastante impresionante el tipo de funcionalidad que puede agregar a sus aplicaciones con solo una línea de código, o unas pocas:

1
2
3
4
5
6
7
// requires...

var app = express();

app.use("/static", express.static(__dirname + "/public"));
app.use(cookieParser('sekret'));
app.use(compress());

Las últimas tres líneas anteriores manejan bastante la funcionalidad de la aplicación web para nosotros. La primera llamada app.use() le dice a Express dónde se encuentran nuestros archivos estáticos y cómo exponerlos, el middleware cookieParser('sekret') maneja todo el análisis de cookies (con cifrado), y el último automáticamente comprime gzip todos nuestros datos de cuerpo HTTP. No está mal para solo tres líneas de código.

Estos middleware son bastante típicos en su aplicación web promedio, pero puede encontrar algunos que hacen más que solo la compresión de datos estándar o el análisis de cookies. Tome estos por ejemplo:

  • casco: ayuda a proteger su aplicación configurando varios encabezados HTTP
  • express-simple-cdn: use fácilmente un CDN para sus activos estáticos
  • unirse-io: une archivos sobre la marcha para reducir el recuento de solicitudes HTTP
  • pasaporte: Adds user authentication to selected routes

Y aquí es una lista mucho más grande de middleware que quizás desee usar.

Ahora que ha visto algunos ejemplos, esto es todo lo que puede hacer con él:

  • Ejecutar cualquier código, incluido el código asíncrono
  • Realizar cambios o adiciones a los objetos de solicitud y respuesta
  • Terminar el ciclo de solicitud-respuesta
  • Llame al siguiente middleware en la pila

Con las infinitas posibilidades, estoy seguro de que tiene algunas ideas propias que le gustaría crear, por lo que a lo largo del resto de este artículo le mostraré cómo escribir su propio middleware. Hay algunos tipos diferentes de middleware que puede escribir (aplicación, enrutador, manejo de errores, etc.), pero en este artículo nos centraremos solo en el nivel de la aplicación.

Lo básico

Se puede pensar en el middleware casi como si fuera una ruta Express. Toman los mismos parámetros y todo, pero a diferencia de las rutas normales, no es necesario que proporcione una ruta URL para el middleware. Las dos mayores diferencias son cómo se trata la ruta y cuándo se llama.

La ruta proporcionada se trata como un prefijo, por lo que si tuviera algo como app.use('/api', ...), entonces su middleware se ejecutará si /api se llama y si /api /users` se llama. Esto es diferente de las rutas donde la ruta debe ser una coincidencia exacta.

La ruta URL se puede omitir de la llamada app.use() si desea que su código se ejecute para todas las solicitudes; de lo contrario, puede especificar una ruta y hacer que su código solo se ejecute cuando esa ruta (y todas sus subrutas) ) se solicita. Por ejemplo, esto podría ser útil para agregar autenticación solo a unas pocas rutas determinadas.

Un middleware simple podría verse así:

1
2
3
4
5
6
var app = express();

app.use(function(req, res, next) {
  console.log('Called URL:', req.url);
  next();
});

Mientras que un controlador de ruta se ve así:

1
2
3
4
5
var app = express();
 
app.get('/', function(req, res, next) {
  res.send('Hey there...');
});

¿Ver? Son básicamente lo mismo, por lo que escribir estas funciones debería resultarle bastante familiar.

Los parámetros utilizados son:

  • req: un objeto que contiene toda la información de solicitud relevante. Esto podría ser cualquier cosa, desde la URL solicitada al cuerpo de una solicitud POST a la dirección IP del usuario.
  • res: este es el objeto de respuesta, que se utiliza para enviar datos al usuario para la solicitud dada. Puede usar esto para enviar un código de respuesta HTTP 404, o para enviar HTML renderizado a través de res.render().
  • siguiente: ​​Y finalmente, el parámetro siguiente es una devolución de llamada para decirle a Express cuando nuestro middleware ha terminado. Si realiza cualquier IO (como llamadas a la base de datos) o un cálculo pesado, es probable que deba hacer que la función sea asíncrona para evitar el bloqueo del hilo de ejecución principal, en cuyo caso tendrá que usar siguiente.

Vale la pena señalar que si su middleware no finaliza el ciclo de solicitud-respuesta con res.end(...) entonces debe llamar a next() para pasar el control al siguiente middleware . Si no lo hace, la solicitud quedará pendiente y se agotará el tiempo de espera.

Un ejemplo

En este ejemplo, crearemos un middleware que lo ayudará a traducir texto automáticamente entre idiomas. Sin embargo, este no es un módulo i18n típico, usaremos Google Translate en su lugar.

Supongamos que ha creado una aplicación de chat que le permite hablar con personas de todo el mundo y, para que sea perfecta, necesita que el texto se traduzca automáticamente. En este caso de uso, la mayoría de los módulos i18n no funcionarían, ya que necesita traducir previamente todas las cadenas, lo que no podemos hacer ya que estamos tratando con la entrada del usuario.

Claro, podría manejar la traducción en cada una de sus rutas Express, o podría hacer que se maneje por usted en el middleware, lo que mantiene su código de ruta más limpio y evita que se olvide de agregar la traducción a cada ruta que lo necesite.

Las cadenas (mensajes de usuario) llegan a través de una API REST, por lo que tendremos que verificar todos los cuerpos de las rutas de la API en busca de texto para traducir. Todas las cadenas que se guarden en la base de datos en las llamadas POST se mantendrán en sus idiomas nativos, pero todas las cadenas que se recuperen de la base de datos con llamadas GET se traducirán al idioma especificado en el encabezado HTTP Accept-Language que acompaña a la solicitud del usuario.

Pensé que no haríamos todos los mensajes en la base de datos en el mismo idioma ya que necesitaríamos traducir algunos de ellos dos veces, lo cual [degrada la calidad de la traducción](https://www.quora.com/What -sucede-cuando-usas-Google-Translate-varias-veces-siguientes-cómo-se-altera-o-afecta-el-idioma).

En primer lugar, escribamos una función simple para llamar a la API de Google Translate:

1
2
3
4
5
6
7
8
var googleTranslate = require('google-translate')('YOUR-API-KEY');

var translate = function(text, lang, cb) {
    googleTranslate.translate(text, lang, function(err, translation) {
        if (!translation) cb(err, null);
        cb(err, translation.translatedText);
    });
}

Luego, usaremos esa función en nuestro código de middleware, que se exporta en modules.export para que lo use la aplicación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = function(req, res, next) {
    if (req.method === 'GET') {
        var lang = 'en';
        var langs = req.acceptsLanguages();
        if (langs[0] !== '*') {
            if (langs[0].length > 2) {
                // ex: en-US
                lang = langs[0].substring(0, 2);
            } else {
                // ex: en
                lang = langs[0];
            }
        }

        if (lang !== res.body.lang) {
            return translate(res.body.message, lang, function(err, translation) {
                res.body.message = translation;
                res.body.lang = lang;
                next();
            });
        }
    }

    next();
};

NOTA: Esta no es realmente la forma de modificar un cuerpo de Respuesta. Solo lo estoy simplificando en aras de la brevedad. Si desea ver cómo modificar realmente el cuerpo, consulte el middleware compresión, que lo hace correctamente. Tienes que enviar las funciones res.write y res.end, lo cual no hice porque solo sería una distracción de los conceptos que estoy tratando de mostrar aquí.

Y finalmente, aplicamos el middleware a nuestra aplicación. Solo asegúrese de llamar a la función app.use después de haber declarado sus rutas. El orden en que se llama es el orden en que se ejecuta cada función.

Además, asegúrese de llamar a next() en cada una de sus rutas /api, de lo contrario, el middleware no se ejecutará.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var expressGoogleTranslate = require('my-translation-middleware');

var app = express();

app.get('/api/message', function(req, res, next) {...});
app.get('/api/message/all', function(req, res, next) {...});
app.post('/api/message', function(req, res, next) {...});
app.delete('/api/message/id', function(req, res, next) {...});

app.use('/api', expressGoogleTranslate);

Y eso es todo lo que hay que hacer. Cualquier cadena devuelta en el cuerpo de la Respuesta que sea un idioma diferente al que el usuario acepta será traducida por Google Translate, que detecta en qué idioma está el texto de origen.

Entonces, si nuestra respuesta comenzó con este aspecto...

1
2
3
4
{
    "message": "The quick brown fox jumps over the lazy dog"
    "lang": "en"
}

... y el usuario solo acepta Swahili, luego de que se ejecute el middleware obtendremos una traducción final que se ve así:

1
2
3
4
{
    "message": "Haraka kahawia mbweha anaruka juu ya mbwa wavivu"
    "lang": "sw"
}

Conclusión

Aunque suene intimidante, el middleware es realmente fácil de crear en Express. Puede usarlo para casi cualquier cosa, sin importar cuán simple o complejo sea.

Solo asegúrese de hacer una búsqueda rápida en npm para lo que sea que esté tratando de hacer, ya que ya hay toneladas de código. Estoy seguro de que ya existe un paquete que hace lo que hace mi código de traducción (y probablemente también mucho mejor).

¿Tiene alguna idea para crear middleware o cómo mejorar mi ejemplo anterior? ¡Cuéntanos en los comentarios!