Llevo años escribiendo esta línea:
const user = req.user;
Compila. TypeScript contento. Y es una mentira con tipo.
Esa propiedad existe nomás si algún middleware corrió antes. ¿Cuál? El que alguien metió en un array, tres archivos arriba, en un orden que nadie escribió en ningún lado. Muevan ese array y tu handler se cae. No en el build. No en los tests. Se cae en prod, un martes, a las tres de la mañana, y el compa que lo movió ya está dormido.
Eso no es tipado. Es rezar con autocompletado.
Ya sé lo que vas a decir
Ay sí, mucho framework nuevo. Otro cabrón que se aburrió de Express y ahora quiere que aprendas sus decoradores. Ya hay doce. Nadie pidió el trece.
Tienes razón en la mitad. La otra mitad es que yo ya había hecho ese error, y se llamaba Expressive Tea.
Lo que maté
Expressive Tea era decoradores más InversifyJS más etapas de arranque, todo montado encima de Express. Funcionaba. Lo usé en cosas reales. Pero cargaba con todo lo que Express arrastra, y para inyectar dependencias tenías que casarte con un contenedor que pesaba más que el problema que resolvía.
Green Tea no es la versión 2. Es la misma idea después de tirar Express y tirar Inversify. Lo que queda de dependencia es reflect-metadata y ya.
Y el cambio de fondo: el centro ya no es la cadena de middlewares. Es el grafo.
Declaras qué necesitas, no cuándo corres
Cada paso dice qué necesita y qué produce. El framework saca el orden solo, revisa el cableado antes de levantar el servidor, y si algo no cuadra no arranca.
@Provider({ provides: 'db' })
class Database {
provide() {
const users = { u1: { id: 'u1', name: 'Diego' } };
return { db: { find: (token: string) => users[token] ?? null } };
}
}
@Step({ provides: 'user', needs: ['db', 'req'] })
class Authenticate {
run(ctx) {
const user = ctx.db.find(ctx.req.headers['x-token']);
if (!user) throw new Unauthorized('invalid token');
return { user };
}
}
@Route('/users')
class UserController {
@Get('/:id')
@Transformer(JsonTransformer)
getUser(@needs('user') user, @param('id') id) {
return { requested: id, you: user };
}
}
La firma del handler es el contrato. @needs('user') no es decoración, es la condición para que ese código exista. Si nada produce user, no compila. No te enteras en prod. Te enteras cuando guardas el archivo.
Y como el orden sale de las dependencias y no de dónde pusiste la línea, las rutas públicas no corren el paso de autenticación. No porque le hayas puesto un if. Porque no lo necesitan y el grafo lo sabe.
Poder verlo
Esta es la parte que a mí me arregló el día. app.explain('/users/:id') te imprime la cadena completa, en orden, tal como va a correr. GET /__graph__ te lo dibuja vivo. app.openapi() te saca el spec 3.1 sin que anotes nada aparte.
Diez años explicándole a la gente el orden de los middlewares dibujando flechitas en una servilleta. Ahora se lo pregunto al programa.
El tiempo real no es un plugin
Un handler que devuelve varios valores a lo largo del tiempo ya es un stream. No hay que inventarle nada. Es la misma forma que devolverías de cualquier función.
@Ssepara mandar del servidor al cliente@Wspara los dos lados@Streamcuando te da igual y que el cliente escoja
Los tres son AsyncIterable. El framing, el backpressure y la limpieza los pone la casa. Tú escribes un generador y te vas.
Corre donde sea
Node, Deno, Bun y Workers. Mismo import, mismo código. Lo único que cambia es la línea de arranque: app.listen, serveDeno, serveBun o edgeHandler. HTTP, SSE y WebSocket se comportan igual en los cuatro, que es más de lo que puedo decir de cualquier cosa que haya portado a Workers en mi vida.
La seguridad viene prendida, no como opción que se te olvida: TLS y WSS, nosniff, X-Frame-Options, HSTS cuando hay https, CORS con preflight vigilado, y límites de tamaño para que no te tiren el server mandándote un JSON de dos gigas.
La parte donde no te vendo nada
Está en beta. Versionado por calendario, YY.M.PATCH-beta.N. Va camino a RC, no está ahí.
Mesh, que es inyección de dependencias entre servicios, donde @needs('billing') te resuelve igual si billing está aquí o en otra máquina, está en alpha y detrás de experimental: true. No mames, dice alfa. Ya si lo metes en producción es tu pedo.
No tiene estrellas, ni comunidad, ni un Discord con roles de colores. Tiene commits, y los tiene porque alguien lleva meses metiéndole en vez de dormir. Si venías buscando adopción masiva, te equivocaste de repo.
npm install @green-tea/core@beta reflect-metadata
No te va a hacer más rápido. No hace nada que no puedas hacer a mano con suficiente disciplina y un documento que nadie va a leer. Lo que hace es que el error salga cuando arranca el proceso y no cuando te habla el cliente.
Yo ya no quiero seguir rezándole a req.user. Por eso lo escribí.
Si le quieres entrar más macizo, la versión larga la escribí en inglés, con el desglose completo y video: I Built a Framework on Express. Then I Had to Rebuild It From Scratch.
El repo: github.com/Expressive-Tea/green-tea



