Si has hecho algún desarrollo significativo de Node en los últimos siete u ocho años, probablemente has usado Express para construir un servidor web en algún momento. Aunque puedes crear un servidor en Node sin usar una librería, no te da mucho fuera de la caja y puede ser bastante engorroso añadir funcionalidad. Express es una librería de servidor minimalista y «sin opinión» y se ha convertido en el estándar de facto para construir aplicaciones web en Node. Para entender Express, necesitas entender Express Middleware.
¿Qué es Express Middleware?
Middleware significa literalmente cualquier cosa que se ponga en medio de una capa del software y otra. Los middleware Express son funciones que se ejecutan durante el ciclo de vida de una petición al servidor Express. Cada middleware tiene acceso al HTTP request
y response
de cada ruta (o camino) a la que está unido. De hecho, el propio Express está compuesto en su totalidad por funciones de middleware. Además, el middleware puede terminar la petición HTTP o pasarla a otra función de middleware usando next
(más sobre esto pronto). Este «encadenamiento» de middleware te permite compartimentar tu código y crear middleware reutilizable.
En este artículo explicaré qué es el middleware, por qué lo usarías, cómo usar middleware Express existente y cómo escribir tu propio middleware para Express.
Requisitos para escribir middleware Express
Hay algunas cosas que necesitarás instaladas para crear, usar y probar middleware Express. En primer lugar, necesitarás Node y NPM. Para asegurarte de que los tienes instalados, puedes ejecutar:
npm -v && node -v
Deberías ver las versiones de Node y NPM que tienes instaladas. Si te da un error, necesitas instalar Node. Estoy usando la última versión de ambos en el momento de escribir este artículo, que es Node 10.9.0 y NPM 6.4.1, pero todos los ejemplos deberían funcionar con las versiones 8+ de Node y 5+ de NPM.
También voy a usar la versión 4.x de Express. Esto es importante porque se hicieron cambios importantes de la versión 3.x a la 4.x.
También será útil tener Postman instalado para probar las rutas que utilizan cualquier verbo HTTP que no sea GET
.
Express Middleware: Lo básico
Para empezar, utilizarás el más básico de los middleware incorporados de Express. Esto te dará la oportunidad de ver cómo se utiliza el middleware, y cómo se estructura el middleware de Express.
Crea un nuevo proyecto y npm init
lo…
npm initnpm install express --save
Crea server.js
y pega el siguiente código:
const express = require('express');const app = express();app.get('/', (req, res, next) => { res.send('Welcome Home');});app.listen(3000);
Ejecuta el servidor a través de node server.js
, accede a http://localhost:3000
, y deberías ver impreso «Welcome Home» en tu navegador.
La función app.get()
es un Middleware a nivel de aplicación. Verás que los parámetros que se pasan al método son req
res
, y next
. Estos son la petición entrante, la respuesta que se está escribiendo, y un método a llamar para pasar la llamada a la siguiente función del middleware una vez que el middleware actual haya terminado. En este caso, una vez enviada la respuesta, la función sale. También podrías encadenar otros middleware aquí llamando al método next()
.
Veamos algunos ejemplos más de los diferentes tipos de middleware.
Ejemplo de middleware de registro de peticiones en Express
En Express, puedes configurar el middleware para que sea un middleware «global»; lo que significa que será llamado para cada petición entrante.
Cambia el contenido de server.js
por:
const express = require('express');const app = express();app.use((req, res, next) => { console.log(req); next();});app.get('/', (req, res, next) => { res.send('Welcome Home');});app.listen(3000);
Esta vez, cuando vayas a http://localhost:3000
deberías ver lo mismo en la ventana del navegador, pero de vuelta en la ventana de la consola verás la salida del objeto de petición entrante.
El middleware cierra la sesión del objeto de petición y luego llama a next()
. El siguiente middleware en la tubería maneja la solicitud de obtención a la URL raíz y devuelve la respuesta de texto. El uso de app.use()
significa que este middleware será llamado para cada llamada a la aplicación.
Ejemplo de tipo de contenido de solicitud Express restringido
Además de ejecutar el middleware para todas las llamadas, también podrías especificar que sólo se ejecute el middleware para llamadas específicas.
Cambia el archivo server.js
de nuevo por:
const express = require('express');const app = express();const requireJsonContent = () => { return (req, res, next) => { if (req.headers !== 'application/json') { res.status(400).send('Server requires application/json') } else { next() } }}app.get('/', (req, res, next) => { res.send('Welcome Home');});app.post('/', requireJsonContent(), (req, res, next) => { res.send('You sent JSON');})app.listen(3000);
Esta vez, inicia el servidor ejecutando:
node server.js
Para probarlo, abre Postman y crea una petición de envío a http://localhost:3000
. No pongas ninguna cabecera y ejecuta la petición. Recibirás el mensaje «Server requires application/json».
Ahora vuelve y añade la cabecera Content-Type
con un valor de application/json
y ejecuta la petición de nuevo. Obtendrás el mensaje «You sent JSON» de vuelta del servidor.
Esta llamada al método app.post()
añade la función de middleware requireJsonContent()
para asegurar que la carga útil de la solicitud entrante tiene un valor de cabecera Content-Type
establecido en application/json
. Si no pasa la comprobación, se envía una respuesta de error. Si lo hace, la solicitud se entrega a la siguiente pieza de middleware en la cadena a través del método next()
.
Medio de terceros Express
Has construido un par de middlewares personalizados hasta ahora, pero hay un montón de paquetes ya construidos para hacer las cosas que normalmente querrías hacer. De hecho, has utilizado la biblioteca de middleware de enrutamiento simple utilizando las funciones de middleware app.get()
o app.post()
. Hay miles de librerías de middleware para hacer cosas como el análisis sintáctico de los datos entrantes, el enrutamiento y la autorización.
Okta tiene un middleware Express para la seguridad de OIDC que te mostraré para demostrar el uso de librerías de middleware de terceros.
Por qué Okta para aplicaciones Express
En Okta, nuestro objetivo es hacer que la gestión de la identidad sea mucho más fácil, más segura y más escalable de lo que estás acostumbrado. Okta es un servicio en la nube que permite a los desarrolladores crear, editar y almacenar de forma segura cuentas de usuario y datos de cuentas de usuario, y conectarlos con una o varias aplicaciones. Nuestra API le permite:
- Autenticar y autorizar a sus usuarios
- Almacenar datos sobre sus usuarios
- Realizar un inicio de sesión social y basado en contraseña
- Asegurar su aplicación con autenticación multifactor
- ¡Y mucho más! Consulta la documentación de nuestro producto
Medio OIDC Express de Okta
Para instalar el medio OIDC de Okta para Express, ejecuta:
npm install @okta/[email protected] --save
En el archivo server.js
, crea una instancia si el middleware con algunas opciones de configuración para que Okta sepa cómo conectarse a tu aplicación Okta.
const oidc = new ExpressOIDC({ issuer: 'https://{yourOktaDomain}/oauth2/default', client_id: '{yourClientId}', client_secret: '{yourClientSecret}', redirect_uri: 'http://localhost:3000/authorization-code/callback', scope: 'openid profile'});
También tendrás que decirle a Express que utilice el router del middleware OIDC en lugar del router por defecto.
app.use(oidc.router);
Entonces lo utilizas como cualquier otro middleware:
app.get('/protected', oidc.ensureAuthenticated(), (req, res) => { res.send('Top Secret');});
La función oidc.ensureAuthenticated()
es un middleware de la librería Okta. Ejecuta una función para ver si el usuario actual está conectado. Si lo está, llama a next()
para que la función app.get()
siga gestionando la petición. Si no lo son devolverá una HTTP 401 (Unauthorized)
respuesta.
El orden del middleware es importante
Cuando Express recibe una petición, cada middleware que coincide con la petición se ejecuta en el orden en que se inicializa hasta que hay una acción de finalización (como el envío de una respuesta).
Así que si se produce un error, todos los middleware que deben manejar los errores serán llamados en orden hasta que uno de ellos no llame a la llamada de la función next()
.
Manejo de errores en el middleware de Express
Express tiene un manejador de errores por defecto incorporado que se inserta al final de la tubería de middleware que maneja cualquier error no manejado que pueda haber ocurrido en la tubería. Su firma añade un parámetro de error a los parámetros estándar de request, response y next. La firma básica tiene el siguiente aspecto:
app.use((err, req, res, next) => { // middleware functionality here})
Para llamar a un middleware de gestión de errores, basta con pasar el error a next()
, así:
app.get('/my-other-thing', (req, res, next) => { next(new Error('I am passing you an error!'));});app.use((err, req, res, next) => { console.log(err); if(!res.headersSent){ res.status(500).send(err.message); }});
En este caso, el middleware de manejo de errores al final del pipeline manejará el error. También puedes notar que he comprobado la propiedad res.headersSent
. Esto sólo comprueba si la respuesta ya ha enviado las cabeceras al cliente. Si no lo ha hecho, envía un estado HTTP 500 y el mensaje de error al cliente. También puedes encadenar middleware de gestión de errores. Esto es común para manejar diferentes tipos de errores de diferentes maneras. Por ejemplo:
app.get('/nonexistant', (req, res, next) => { let err = new Error('I couldn\'t find it.'); err.httpStatusCode = 404; next(err);});app.get('/problematic', (req, res, next) => { let err = new Error('I\'m sorry, you can\'t do that, Dave.'); err.httpStatusCode = 304; next(err);});// handles not found errorsapp.use((err, req, res, next) => { if (err.httpStatusCode === 404) { res.status(400).render('NotFound'); } next(err);});// handles unauthorized errorsapp.use((err, req, res, next) => { if(err.httpStatusCode === 304){ res.status(304).render('Unauthorized'); } next(err);})// catch allapp.use((err, req, res, next) => { console.log(err); if (!res.headersSent) { res.status(err.httpStatusCode || 500).render('UnknownError'); }});
En este caso, el middleware comprueba si se ha lanzado un error 404 (no encontrado). Si es así, renderiza la página de plantilla ‘NotFound’ y luego pasa el error al siguiente elemento del middleware. El siguiente middleware comprueba si se ha producido un error 304 (no autorizado). Si es así, muestra la página «No autorizada» y pasa el error al siguiente middleware de la cadena. Por último, el gestor de errores «catch all» simplemente registra el error y, si no se ha enviado ninguna respuesta, envía el httpStatusCode
del error (o un estado HTTP 500 si no se proporciona ninguno) y muestra la plantilla ‘UnknownError’.
Aprenda más sobre Express Middleware
Para obtener instrucciones detalladas sobre la configuración del middleware Okta OIDC, puede seguir el ExpressJS Quickstart.
También hay una lista de middleware Express soportado oficialmente en este repo de GitHub que puedes probar y profundizar para aprender más
Por último, si estás interesado en aprender más sobre cómo usar Okta, hay un SDK de Okta Node para implementar más funcionalidad de gestión de usuarios en tu aplicación.