Mantenimiento y migración a Express.js 5 | Nicolás Garzón
Migrar una major version no consiste en cambiar un número y reparar el primer error. Debes revisar runtime, routing, middleware, contratos, tipos, tests y operación como un cambio coordinado.
Express 5 es la referencia moderna del notebook. A julio de 2026, npm publica Express 5.2.1 como etiqueta latest; Express 4.22.2 continúa como latest-4. Las ramas 5.x y 4.x siguen soportadas oficialmente, aunque solo la versión más reciente de cada major recibe soporte. Express 5 requiere Node.js 18 o superior.
Una aplicación Express depende de más que el paquete principal:
Texto
Copiar Node.js
+ Express
+ path matching
+ middleware externo
+ @types/express
+ tests
+ proxy/deploymentUn cambio puede compilar y aun romper rutas, errores, MIME types o seguridad en producción. La migración necesita inventario y evidencia.
Lleva Express 4 a su última versión soportada.
Elimina deprecations conocidas.
Actualiza Node a una rama compatible y soportada.
Asegura tests HTTP representativos.
Inventaría middleware y adaptadores.
Guarda baseline de rutas, errores y rendimiento.
Prepara rollback con lockfile y artifact anterior.
Migrar desde una versión 4 antigua directamente aumenta variables.
La guía oficial ofrece una receta:
Bash
Copiar npx codemod@latest @expressjs/v5-migration-recipeLos codemods corrigen patrones mecánicos, no comprenden el negocio. Revisa el diff, ejecuta formatter, typecheck y tests. No reemplazan la migration guide.
En Express 5, un handler o middleware que retorna una Promise propaga su rechazo a next(error):
TypeScript
Copiar app. get ( '/orders/:id' , async ( request, response) => {
const order = await orders. findById ( request. params. id) ;
response. json ( order) ;
} ) ; Esto permite retirar wrappers como asyncHandler si ya no aportan otra función. Pero solo aplica al trabajo que forma parte de la Promise retornada.
TypeScript
Copiar app. post ( '/jobs' , async ( _request, response) => {
startDetachedWork ( ) ;
response. sendStatus ( 202 ) ;
} ) ; Revisa wrappers existentes para evitar doble next(error) o comportamiento duplicado.
Express 5 actualiza path-to-regexp. Las rutas string necesitan revisión.
TypeScript
Copiar
app. get ( '/*' , handler) ;
app. get ( '/*splat' , handler) ; Para incluir la raíz, la guía utiliza braces:
TypeScript
Copiar app. get ( '/{*splat}' , handler) ; La sintaxis ? se reemplaza por braces:
TypeScript
Copiar
'/:file.:ext?'
'/:file{.:ext}' Patrones que utilizaban caracteres especiales o expresiones dentro del string deben reescribirse como rutas explícitas o arrays.
No migres rutas solo por inspección; prueba una tabla de paths válidos e inválidos.
La guía oficial enumera signatures heredadas retiradas. Ejemplos a revisar:
res.send(status).
res.sendfile() en favor de res.sendFile().
Signatures antiguas de res.send, res.json y res.redirect con status.
app.del() en favor de app.delete().
Acceso a app.router heredado.
req.param().
TypeScript
Copiar
res. send ( 201 ) ;
res. sendStatus ( 201 ) ;
res. status ( 201 ) . send ( body) ; Busca usos con codemod, grep y typecheck.
Sin un parser compatible, req.body queda undefined en Express 5, en lugar de un objeto vacío en ciertos comportamientos anteriores.
Esto es más honesto, pero rompe código que asume {}:
TypeScript
Copiar if ( Object. keys ( request. body) . length === 0 ) { ... } Registra parsers antes de las rutas y valida unknown.
req.query pasa a ser un getter y el parser predeterminado es el modo simple. Aplicaciones que dependían de objetos anidados o mutaban req.query necesitan corregirse.
No dependas de parsing complejo implícito. Define una convención y valida la forma resultante.
Express 5 conserva el puerto en req.host. Código que esperaba solo hostname debe utilizar la propiedad adecuada o normalizar explícitamente.
Para URLs públicas sensibles, utiliza configuración en lugar de Host no confiable.
El status debe ser un entero entre 100 y 999; valores inválidos lanzan error. Esto revela bugs antes ocultos.
Valida cualquier mapping dinámico y no uses status del cliente directamente.
Express 5 ignora maxAge y expires enviados a clearCookie; la limpieza debe usar las mismas opciones de alcance —path, domain, sameSite, secure— con las que se creó la cookie.
Prueba logout en el navegador/topología real.
En Express 5, el callback recibe errores de escucha en vez de que algunos errores se lancen fuera del callback:
TypeScript
Copiar const server = app. listen ( port, ( error) => {
if ( error) {
logger. error ( { error } , 'Listen failed' ) ;
return ;
}
logger. info ( 'Server started' ) ;
} ) ; También puedes usar createServer(app) y escuchar el evento error directamente.
Express 5 utiliza el paquete mime-types en APIs como res.sendFile y express.static, por lo que algunos Content-Type pueden cambiar respecto a Express 4.
Prueba downloads, assets, cache keys y consumidores que comparan headers exactamente.
Error middleware con cuatro argumentos.
headersSent.
Wrappers async redundantes.
Callbacks que todavía requieren next(error).
Error contract estable.
Tests de rejected Promises.
Una migración es una oportunidad para separar errores esperados de fallos de programación, pero no cambies todo el contrato a la vez sin necesidad.
Para cada paquete confirma:
Compatibilidad con Express 5.
Mantenimiento reciente.
Tipos compatibles.
Uso de APIs eliminadas.
Semántica de errores async.
Advisories.
Prueba especialmente sessions, uploads, auth, rate limiting, compression, OpenAPI y testing.
Texto
Copiar express 5.x
@types/express 5.x
Node types compatibles
TypeScript/config ESMLos tipos pueden revelar signatures retiradas, pero no detectan cambios de route matching ni runtime parsing.
Tests verdes.
Inventario de rutas.
Métricas actuales.
Express 4 actualizado.
Node compatible.
CI/Docker/plataforma alineados.
Codemods.
APIs retiradas.
Typecheck.
Rutas.
Query/body.
Errors y promises.
Cookies/files.
Integration/E2E.
Load test de rutas críticas.
Canary.
Retirar wrappers y compatibilidad temporal.
Actualizar docs/runbooks.
DomiSys tiene fallback SPA app.get('*', ...), rutas opcionales y un wrapper async.
Cambiar fallback a /{*splat}.
Reescribir params opcionales con braces.
Ejecutar route matrix para /, /api, assets y rutas desconocidas.
Confirmar que wrapper async no duplica propagación.
Probar JSON ausente/incorrecto.
Probar session logout y uploads.
Canary observa 404/500 y route distribution.
Artifact Express 4.
Lockfile.
Schema compatible.
Config sin cambios incompatibles.
Feature flags cuando ayudan.
Si migración incluye cambio destructivo de DB o auth, el rollback deja de ser simple. Separa esos cambios.
404 por route.
400/415 de parsing.
500/error types.
Latencia.
Memory/CPU.
Login/logout.
Static/download Content-Type.
Rejected promises.
Un aumento de 404 puede indicar matching roto, no tráfico inválido.
Cada ruta crítica y método.
Wildcards/optional params.
Params/query/body edge cases.
Async rejection.
Error after headers.
Cookies/session.
Uploads/static files.
Proxy/host.
404/405.
Startup y shutdown.
No detecta cambios de runtime.
Puede propagar dos veces o esconder stacks.
Dificulta atribuir fallos. A veces es necesario, pero entonces aumenta testing/canary.
Es uno de los cambios más relevantes.
No entiende contrato ni middleware.
Crea una falsa sensación de compatibilidad.
Express 4 sigue soportado oficialmente. Puede ser razonable si una dependencia crítica no es compatible y existe un plan de migración con owner y fecha.
No uses esto para posponer indefinidamente updates de seguridad. Mantén la última 4.x y monitorea soporte.
Express 5 requiere Node 18+.
Las rutas string necesitan una revisión deliberada.
Promise rejection automática cubre trabajo retornado.
Body/query, cookies, MIME y APIs heredadas pueden cambiar.
Codemods ayudan, pero tests deciden.
Migra con baseline, canary y rollback.
Mantén únicamente versiones soportadas.
¿Por qué /* necesita cambiar?
¿Qué trabajo async no captura Express 5?
¿Por qué req.body puede romper código previo?
¿Qué prueba revela route matching?
¿Cuándo permanecer temporalmente en 4.x puede ser válido?
Ver respuestas
Los wildcards deben tener nombre; para raíz se usan braces.
Tareas desprendidas que no forman parte de la Promise retornada.
Puede ser undefined sin parser, no un objeto vacío asumido.
Una matriz de método/path con matches y 404 esperados.
Cuando una dependencia crítica bloquea la migración y existe plan activo mientras se mantiene la última versión soportada.
Antipatrones de Express con contexto convierte los errores recurrentes del notebook en decisiones prácticas, no prohibiciones absolutas.