Getting started
Scaffold it
Section titled “Scaffold it”The fastest path is matcha, green-tea’s CLI. It writes a project that runs on the first command — no wiring, no config:
matcha new my-api # --runtime node (default) | deno | buncd my-api && matcha runOr install by hand
Section titled “Or install by hand”npm install @green-tea/core reflect-metadata# optional, only if you use them:npm install ws # WebSocket routes (@Ws) and meshnpm install busboy # multipart/form-data file uploadsgreen-tea runs on Node ≥ 18, Deno, Bun, and the edge — the same app, you only swap the entry point. This guide uses Node; see runtimes for the others and for what the edge can’t offer.
green-tea uses legacy TypeScript decorators. Enable them in your tsconfig.json:
{ "compilerOptions": { "experimentalDecorators": true, "target": "es2020", "moduleResolution": "node" }}You do not need emitDecoratorMetadata — green-tea records argument positions explicitly. See Why legacy decorators for the rationale.
Your first app
Section titled “Your first app”A provider produces a value, a step transforms the request context, and a controller handler declares exactly what it needs:
import { createApp, Provider, Step, Route, Get, Module, Unauthorized, needs, param,} from '@green-tea/core';
@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'); // cut the request return { user }; // continue }}
@Route('/users')class UserController { @Get('/:id') getUser(@needs('user') user, @param('id') id) { // the signature IS the contract return { requested: id, you: user }; }}
@Module({ mountpoint: '/api', providers: [Database], steps: [Authenticate], controllers: [UserController] })class ApiModule {}
const app = createApp({ modules: [ApiModule] });console.log(app.explain('/api/users/:id')); // auditable: the ordered chain, with originsapp.listen(3000);curl -H 'x-token: u1' http://localhost:3000/api/users/9# {"requested":"9","you":{"id":"u1","name":"Diego"}}The handler asked for user via @needs('user'). If nothing in the graph produced user, createApp would throw at boot with a “did you mean…” hint — you never serve undefined.
Where to next
Section titled “Where to next”- The dependency graph — the mental model behind everything.
- Dependency injection — providers, steps, and modules in depth.
- Argument decorators — everything a handler can inject.
- Streaming & real-time — SSE, WebSocket, and ndjson from one primitive.