Express.js
Application factory y startup
Explica cómo separar construcción de la app, conexión de dependencias y arranque del servidor para controlar startup, errores, testing y graceful shutdown.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Express.js
Explica cómo separar construcción de la app, conexión de dependencias y arranque del servidor para controlar startup, errores, testing y graceful shutdown.
Una application factory construye el pipeline sin efectos de red. El startup conecta configuración, dependencias y lifecycle del proceso en un orden verificable.
load config
↓
create infrastructure
↓
create modules
↓
create Express app
↓
create HTTP server
↓
listenSeparar construcción de ejecución permite tests, múltiples entry points y shutdown ordenado.
export type AppDependencies = {
logger: Logger;
orders: OrdersModule;
authentication: RequestHandler;
};
export function createApp(deps: AppDependencies): Express {
const app = express();
app.disable('x-powered-by');
app.use(requestContext(deps.logger));
app.use(express.json({ limit: '100kb' }));
app.use(deps.authentication);
app.use('/orders', deps.orders.router);
app.use(notFoundHandler);
app.use(errorHandler(deps.logger));
return app;
}No lee env, no abre DB y no escucha.
export async function createRuntime(config: AppConfig): Promise<Runtime> {
const logger = createLogger(config.logging);
const pool = createPool(config.database);
await verifyDatabase(pool);
const orders = createOrdersModule({ pool, logger });
const app = createApp({ logger, orders, authentication: createAuth(config) });
const server = createServer(app);
return {
app,
server,
async close() {
await orders.close();
await pool.end();
},
};
}La factory devuelve recursos y cleanup.
async function main() {
const config = loadConfig(process.env);
const runtime = await createRuntime(config);
runtime.server.listen(config.runtime.port, config.runtime.host, () => {
runtime.logger.info({ port: config.runtime.port }, 'Server started');
});
installShutdownHandlers(runtime);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Solo el entry point invoca main.
Construye primero piezas sin dependencias y después consumidores. Ejemplo:
config
→ logger
→ pool / redis / clients
→ repositories / gateways
→ use cases
→ routers
→ app
→ serverEsto hace visible el grafo y evita imports circulares.
Si Redis abre y PostgreSQL falla, cierra Redis antes de salir. La factory puede usar un stack de cleanup o construir de forma que cada error libere lo previo.
No dejes procesos vivos con timers o sockets después de fallar.
Decide entre:
La base transaccional suele ser crítica; un proveedor de email asíncrono no.
Evita ejecutar migraciones concurrentes desde cada pod. Opciones:
El startup puede verificar versión mínima de schema sin modificarla.
const app = createApp({
logger: silentLogger,
orders: fakeOrdersModule,
authentication: fakeAuth(actor),
});No importa server.ts, por lo que no hay puertos o env global.
Los mismos casos de uso pueden ejecutarse desde:
Cada entry point tiene su composition root y lifecycle, reutilizando módulos sin importar Express en el dominio.
La factory puede exponer readiness checks:
return {
app,
server,
health: {
database: () => ping(pool),
workers: () => orders.workersHealthy(),
},
close,
};La app no necesita buscar globals.
Activa source maps en producción para stacks útiles y captura errores de inicialización con contexto seguro. No registres secrets ni config completa.
DomiSys inicia:
Si Redis opcional falla, módulo de realtime queda degradado; pedidos siguen si contrato lo permite.
close es idempotente.app.listen dentro de app.ts.TypeScript con Express define tipos en boundaries sin confundirlos con validación runtime.