9 min read

Héroes anónimos de serverless: 4 middlewares plug & play que salvarán tus endpoints

Héroes anónimos de serverless: 4 middlewares plug & play que salvarán tus endpoints

Capítulo 1: Cuando el cliente golpea tus lambdas

Era un día cualquiera en el inesperado mundo de la consultoría, cuando (como siempre) un cliente escribe reclamando que el endpoint que desarrollaste en serverless no funciona. Estás seguro que tu código es infalible, por lo que le bajas la gravedad y dices: "De seguro es error de él, mi código siempre funciona bien."

Abres los logs y te encuentras un 500. Das por hecho que es un caso muy específico que ni los test más paranoicos podrían haber previsto, pero al buscar la causa terminas quemando horas de desarrollo y litros de café para un error que no quiere aparecer.

Finalmente lo encuentras: El cliente envió un body vacío, provocando que el código fallara al no poder parsear el null. Siendo justos, el problema era del cliente por no enviar la data, aunque tal vez también fue tuyo por no retornar el mensaje de error adecuado.

En un impulso de supervivencia para no seguir quemando horas y mantener la rentabilidad del proyecto, realizas este clásico truco desesperado:

const body = JSON.parse(event.body || "{}");

Sabes que esto soluciona el problema (junto con un par de avemarías para desear que con eso baste), pero te queda ese pequeño remordimiento de haber usado un workaround indigno.

Con el tiempo descubres que no estabas solo... todos esos problemas (los null, los headers inconsistentes, los events extraños) ya tienen soluciones probadas, simples y lo que es mejor: son plug & play.

En este artículo veremos soluciones que requieren una sola línea de código y que, con el tiempo, se han convertido en mis cuatro compañeros más fieles a la hora de trabajar con middlewares en Serverless.

Capítulo 2: Los 4 guardianes del caos en serverless

Antes de conocer a los cuatro guardianes, vale la pena hablar del punto en común que los une: Middy.

Esta librería funciona como el motor de orquestación de los middlewares en entornos Serverless, permitiendo interceptar, transformar o validar los eventos antes de que lleguen a la lógica principal del handler.

En artículos anteriores he mostrado cómo usar Middy para construir mis propios middlewares personalizados (como autenticación, validación con Zod o Rate Limits), pero en esta ocasión daremos un paso atrás para reconocer a los héroes silenciosos que ya vienen listos para usar.

2.1 El traductor universal: http-json-body-parser

Aquí no hay mucho misterio: el error es exactamente el que vimos en el Capítulo 1: A veces el body llega como string, otras como objeto… y, en los peores días, ni siquiera llega. Cuando eso pasa, solemos recurrir a una función genérica como esta:

function parseBody(body: string | Record<string, unknown>) {
  if (!body) return {};
  if (typeof body === "object") return body;
  return JSON.parse(body);
}

Como verás, son muchos casos que validar, y al hacerlo manualmente tu código empezará a crecer de manera innecesaria para poder tomar todos los casos. ¿Cómo no olvidar el típico SyntaxError: Unexpected token 1 in JSON del demonio que da más de un dolor de cabeza?

Esto se soluciona instalando este middleware:

npm i @middy/http-json-body-parser

Lo que hace es interceptar el evento antes de que llegue a tu handler y convierte el event.body automáticamente en un objeto. Si el contenido no es válido o el encabezado Content-Type no corresponde, retornará un 415: Unsupported Media Type para mantener la integridad del flujo.

Implementarlo es tan simple como:

export const main = middy(handler)
  .use(httpJsonBodyParser());
http-json-body-parser | Middy.js
This middleware automatically parses HTTP requests with a JSON body and converts the body into an

2.2 El diplomático interestelar: http-header-normalizer

Este es el clásico problema cuando necesitas acceder al header: a veces la key viene con mayúscula y otras con minúscula. No entiendes el por qué, así que solo optas por crear una función por cada elemento a utilizar. Por ejemplo:

function getAuthorizationHeader(headers: Record<string, unknown>) {
  return headers["authorization"] || headers["Authorization"];
}

Hacer esto resulta engorroso, ya que por cada variable del header debes crear su función para mayúscula y minúscula.

Aquí viene el dato freak del día 🤓☝️: En la RFC7230 se especifica que los nombres de los headers no distinguen mayúsculas de minúsculas. Dicho de forma textual (y traducida):

“Cada campo del header consiste en un campo case-insensitive a mayúsculas y minúsculas seguido de dos puntos (':') …”
- RFC 7230, sección 3.2 (Header Fields)

Es decir: Authorization, authorization o incluso AUTHORIZATION son, en esencia, el mismo campo.

Es aquí donde nuestro middleware entra en acción. La forma de instalarlo es la siguiente:

  npm i @middy/http-header-normalizer

Ahora, todos nuestros problemas se solucionan, porque este middleware estandariza todos los nombres de headers a minúsculas, permitiendo que tu código se centre en la lógica y no en la semántica de capitalización.

Para añadirlo, solo basta con:

export const main = middy(handler)
  .use(httpHeaderNormalizer());
http-header-normalizer | Middy.js
This middleware normalizes HTTP header names. By default, it normalizes to lowercase. It

2.3 El policía del multiverso: http-security-headers

Por lo general, tendemos a ser un poco indiferentes a los headers de seguridad... Hasta que nos toca la famosa "auditoría de endpoints" y la reprobamos estrepitosamente.

Cuando eso ocurre, recordamos (o aprendemos) que existe el OWASP10, una guia creada para ayudarnos a identificar las vulnerabilidades más comunes y fortalecer nuestras aplicaciones antes de que alguien más las descubra.

💡
OWASP significa Open Worldwide Application Security Project, y es una organización sin fines de lucro dedicada a mejorar la seguridad del software en todo el mundo.

Aquí solemos tomar una de dos rutas: Ignorar el problema hasta que una auditoría golpea la puerta o crear una función manual con todas las cabeceras sugeridas por OWASP. Por ejemplo:

export const HTTP_SECURITY_HEADERS = {
	"X-Frame-Options": "DENY",
	"X-Content-Type-Options": "nosniff",
	"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
	"Permissions-Policy": "geolocation=(), camera=(), microphone=()",
	// Aquí más security headers
};

El resultado es un código difícil de mantener y una lista que nunca termina de estar completa. Pero no hay razón para complicarse la vida, con un simple comando puedes activar un escudo automático en cada respuesta HTTP:

npm i @middy/http-security-headers

Gracias a esto, se refuerza cada respuesta con un conjunto de cabeceras de seguridad estándar. No interrumpe tu flujo ni modifica tu lógica: simplemente levanta un escudo a tu alrededor.

La forma de añadirlo es esta:

export const main = middy(handler)
  .use(httpSecurityHeaders());
http-security-headers | Middy.js
Applies best practice security headers to responses. It’s a simplified port of HelmetJS. See HelmetJS documentation for more details.

2.4 El reconstructor: http-event-normalizer

Este caso es más sutil, pero igual de peligroso: Imagina que tienes un endpoint que recibe un parámetro por query, por ejemplo userId. Todo funciona bien, hasta que el usuario no envía ninguno.

Cuando eso ocurre, el objeto queryStringParameters simplemente no existe, dejándonos un bonito undefined que explota en el peor momento:

const userId = event.queryStringParameters.userId

De pronto, tu función falla sin aviso, no por un error lógico, sino porque la estructura del evento nunca fue la que esperabas.

Podrías envolverlo todo en validaciones defensivas, condicionales y ?. por doquier, pero al final terminarías gastando más tiempo reparando inconsistencias que resolviendo la misión principal de tu función.

Por suerte, la estabilidad está a solo una librería de distancia:

npm i @middy/http-event-normalizer

Este middleware repara la estructura del evento antes de que lo toques, asegurando que todas las propiedades existan, con los tipos correctos y sin valores indefinidos. Después de aplicarlo, queryStringParameters y pathParameters siempre estarán presentes, aunque vacíos.

Para usarlo, basta con añadir la siguiente línea de código:

export const main = middy(handler)
  .use(httpEventNormalizer());
http-event-normalizer | Middy.js
If you need to access the query string or path parameters in an API Gateway event you

Capítulo 3: La estabilidad del sistema

3.1 El orden de ejecución

En el mundo de los middlewares, el orden importa tanto como en un viaje temporal: si alteras el orden, podrías romper la línea de tiempo de tu Lambda.

Middy ejecuta los middlewares de forma secuencial (de arriba hacia abajo) antes de llamar a tu handler. Por eso, si httpJsonBodyParser() no va antes de tu lógica, tu event.body seguirá siendo un string; o si httpEventNormalizer() se ejecuta después de httpJsonBodyParser(), tal vez intente corregir propiedades que aún no existen.

El orden en que recomiendo utilizar estos middlewares es el siguiente:

export const main = middy(handler)
  .use(httpEventNormalizer())
  .use(httpHeaderNormalizer())
  .use(httpJsonBodyParser())
  .use(httpSecurityHeaders());
  1. httpEventNormalizer(): Debe ir primero porque garantiza la estructura base del evento antes de que cualquier otro middleware lo manipule. Asegura que headers, queryStringParameters y pathParameters existan (aunque estén vacíos), evitando errores en los middlewares que vienen después.
  2. httpHeaderNormalizer(): Una vez que el evento está completo, este middleware uniforma los encabezados, convirtiendo todos los nombres en minúsculas para que sean consistentes. Esto es importante antes de aplicar cualquier middleware (como el httpJsonBodyParser) que dependa de Content-Type.
  3. httpJsonBodyParser(): Ahora que el evento tiene su estructura establecida y los headers ya están normalizados, este middleware puede parsear el body con total seguridad. Si Content-Type indica JSON, convierte el body a un objeto; de lo contrario, lo deja tal cual.
  4. httpSecurityHeaders(): Finalmente, este middleware se ejecuta después del handler, reforzando la respuesta con el estándar de security headers (sin alterar la ejecución principal).

3.2 El wrapper es la única llamada al equilibrio

Repetir cuatro .use() en cada Lambda es funcional, pero poco elegante. Tu código termina viéndose pesado, como una nave con exceso de módulos acoplados.

import middy from "@middy/core";
import httpJsonBodyParser from "@middy/http-json-body-parser";
import httpHeaderNormalizer from "@middy/http-header-normalizer";
import httpSecurityHeaders from "@middy/http-security-headers";
import httpEventNormalizer from "@middy/http-event-normalizer";

const handler = async event => {
  // Aquí la lógica del handler...
}

export const main = middy(handler)
  .use(httpEventNormalizer())
  .use(httpHeaderNormalizer())
  .use(httpJsonBodyParser())
  .use(httpSecurityHeaders());

La mejor práctica es crear un wrapper que encapsule estos middlewares en un solo punto.

import httpJsonBodyParser from "@middy/http-json-body-parser";
import httpHeaderNormalizer from "@middy/http-header-normalizer";
import httpSecurityHeaders from "@middy/http-security-headers";
import httpEventNormalizer from "@middy/http-event-normalizer";

export const wrapperMiddleware = [
	httpEventNormalizer(),
	httpHeaderNormalizer(),
	httpJsonBodyParser(),
	httpSecurityHeaders()
];

De esta forma, solo exportamos y usamos el wrapperMiddleware, dejando nuestro código de esta manera

import middy from "@middy/core";
import { wrapperMiddleware } from "../middlewares/wrapper-middleware.middleware.ts"

const handler = async event => {
  // Aquí la lógica del handler...
}

export const main = middy(handler)
  .use(wrapperMiddleware);

Con un solo paso, garantizas que todas las funciones viajen bajo los mismos protocolos de estabilidad.

Capítulo 4: El Escuadrón Invisible

Estos cuatro middlewares son como los androides secundarios de una película de ciencia ficción: no aparecen en los carteles, pero sin ellos la misión fracasa antes del segundo acto.

En un mundo serverless donde cada milisegundo cuenta y cada bug cuesta un cold start, tener herramientas plug & play que limpian, normalizan y protegen tus eventos es la diferencia entre un caos interestelar y un sistema estable.

Así que la próxima vez que arranques una nueva Lambda, recuerda invocar a estos héroes silenciosos. No hacen magia… pero a veces, se siente como si sí.


Referencias:

Middy, the stylish Node.js middleware engine for AWS Lambda
Middy is a Node.js middleware engine for AWS Lambda that lets you organise your Lambda code, remove code duplication, and focus on business logic!
RFC 7230: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol for distributed, collaborative, hypertext information systems. This document provides an overview of HTTP architecture and its associated terminology, defines the “http” and “https” Uniform Resource Identifier (URI) schemes, defines the HTTP/1.1 message syntax and parsing requirements, and describes related security concerns for implementations.
OWASP Secure Headers Project | OWASP Foundation
Provides technical information about HTTP security headers.