Saltearse al contenido

Adaptador Express

El adaptador Express de Veloce-TS te permite montar una aplicación Veloce-TS dentro de una app Express.js existente — o usar Express como capa de transporte HTTP. Es útil cuando migras de Express a Veloce-TS de forma incremental, o cuando necesitas middleware exclusivo de Express (Passport.js, multer, sesiones, etc.).

:::note Reescritura en v0.4.0 El adaptador Express fue completamente reescrito en v0.4.0 para compatibilidad total con ESM, manejo correcto del body y delegación apropiada de errores. Si usabas una versión anterior, revisa las notas de migración. :::


Express es una peer dependency — instálala junto a Veloce-TS:

Ventana de terminal
# Usando Bun
bun add veloce-ts express
bun add -d @types/express
# Usando npm
npm install veloce-ts express
npm install --save-dev @types/express

import express from 'express';
import { VeloceTS, Controller, Get, Post, Body } from 'veloce-ts';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
import { z } from 'zod';
// 1. Define tu app Veloce-TS normalmente
const UsuarioSchema = z.object({
nombre: z.string().min(2),
email: z.string().email(),
});
@Controller('/usuarios')
class UsuarioController {
@Get('/')
async listar() {
return [{ id: 1, nombre: 'Juan' }];
}
@Post('/')
async crear(@Body(UsuarioSchema) usuario: z.infer<typeof UsuarioSchema>) {
return { id: 2, ...usuario };
}
}
const veloce = new VeloceTS({ title: 'Mi API', docs: true });
veloce.include(UsuarioController);
await veloce.compile();
// 2. Crea una app Express y monta Veloce-TS
const app = express();
app.use(express.json());
// Monta el adaptador — maneja todas las peticiones bajo /api
const adapter = new ExpressAdapter(veloce);
app.use('/api', adapter.getHandler());
// Puedes tener rutas Express normales junto a las de Veloce-TS
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000, () => {
console.log('Servidor en http://localhost:3000');
console.log('Rutas Veloce-TS en http://localhost:3000/api');
console.log('Swagger UI en http://localhost:3000/api/docs');
});

El adaptador v0.4.0 acepta una app Express pre-creada como segundo argumento. Esto permite agregar middleware (auth, rate limiting, multipart, etc.) que se ejecuta antes de que las peticiones lleguen a Veloce-TS:

import express from 'express';
import passport from 'passport';
import multer from 'multer';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
const expressApp = express();
// Agrega el middleware Express que necesites
expressApp.use(express.json());
expressApp.use(express.urlencoded({ extended: true }));
expressApp.use(passport.initialize());
const upload = multer({ dest: 'uploads/' });
// Crea el adaptador pasando la app Express pre-configurada
const adapter = new ExpressAdapter(veloce, expressApp);
expressApp.use('/api', adapter.getHandler());
expressApp.listen(3000);

Monta Veloce-TS en cualquier sub-ruta de tu app Express:

const mainApp = express();
mainApp.use(express.json());
// API v1 con Veloce-TS
const v1Adapter = new ExpressAdapter(veloceV1App);
mainApp.use('/api/v1', v1Adapter.getHandler());
// API v2 con otra app Veloce-TS
const v2Adapter = new ExpressAdapter(veloceV2App);
mainApp.use('/api/v2', v2Adapter.getHandler());
// Rutas legadas en Express
mainApp.use('/legacy', legacyRouter);
mainApp.listen(3000);

El adaptador v0.4.0 delega correctamente los errores inesperados al pipeline de errores de Express via next(err):

import express, { Request, Response, NextFunction } from 'express';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
const app = express();
app.use(express.json());
const adapter = new ExpressAdapter(veloce);
app.use('/api', adapter.getHandler());
// Este handler de errores se llama para errores no manejados
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error('Error no manejado:', err);
res.status(500).json({
error: 'Error interno del servidor',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
});
});
app.listen(3000);

El adaptador v0.4.0 maneja correctamente tanto bodies raw (Buffer) como pre-parseados:

  • JSON/urlencoded: Si el middleware Express ya parseó el body, el adaptador usa el valor parseado
  • Buffer raw: Si el body es raw (p.ej. webhooks, subida de archivos), se pasa correctamente
  • Header transfer-encoding: El adaptador omite este header al reenviar respuestas (era fuente de bugs en versiones anteriores)

Ahora las respuestas se envían por chunks, así que SSE y las exportaciones en streaming se comportan igual detrás de Express que directamente sobre Bun:

@Controller('/events')
class EventsController {
@Get('/')
@SSE()
async *stream() {
for await (const event of source) {
yield { data: event }; // llega al cliente de inmediato
}
}
}

Se respeta la contrapresión: cuando res.write() indica que el buffer del socket está lleno, el adaptador espera el evento drain antes de enviar el siguiente chunk en lugar de bufferizar sin límite.

listen() además ahora devuelve un ServerInstance real, así que close() es awaitable:

const server = adapter.listen(3000);
await server.close();

// Antes (v0.3.x)
import { ExpressAdapter } from 'veloce-ts';
// Ahora (v0.4.0)
import { ExpressAdapter } from 'veloce-ts/adapters/express';

El adaptador ya no necesita workarounds como declare const require: any. Usa Function('return require')() internamente para cargar Express de forma lazy, lo que funciona perfectamente en proyectos ESM.


EscenarioRecomendación
Proyecto nuevoUsar Veloce-TS standalone (adaptador Hono) para mejor rendimiento
Migrar una app Express a Veloce-TSUsar el adaptador para migrar ruta por ruta
Necesitas Passport.js, multer u otro middleware solo de ExpressUsar el adaptador
Plataforma que solo soporta ExpressUsar el adaptador
Necesitas máximo rendimientoUsar Veloce-TS standalone