Your First StreetJS API

Level: Beginner · Time: ~15 minutes · Prerequisites: Node.js ≥ 20

By the end you will have a running HTTP API with a controller, a service, and dependency injection — built on Node.js core, no Express.


1. Install and scaffold

1
2
3
4
npm install -g @streetjs/cli
street create hello-street
cd hello-street
npm install

This generates a complete project: src/main.ts, example controllers, a health check, Docker files, and a street.config.ts. Start it:

1
2
street dev
# [street] Listening on http://0.0.0.0:3000
1
2
curl http://localhost:3000/health
# {"status":"ok","uptime":1.2,...}

2. Add a controller

Controllers group routes under a path prefix. Create src/controllers/greeting.controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Controller, Get, Post } from 'streetjs';
import type { StreetContext } from 'streetjs';

@Controller('/api/greet')
export class GreetingController {
  @Get('/')
  async hello(ctx: StreetContext): Promise<void> {
    ctx.json({ message: 'Hello from StreetJS!' });
  }

  @Get('/:name')
  async helloName(ctx: StreetContext): Promise<void> {
    const name = ctx.params['name'] ?? 'stranger';
    ctx.json({ message: `Hello, ${name}!` });
  }

  @Post('/')
  async echo(ctx: StreetContext): Promise<void> {
    ctx.json({ received: ctx.body }, 201);
  }
}

Register it in src/main.ts next to the existing controllers:

1
2
3
import { GreetingController } from './controllers/greeting.controller.js';
// ...
app.registerController(GreetingController);

Restart and test:

1
2
3
4
5
curl http://localhost:3000/api/greet
# {"message":"Hello from StreetJS!"}

curl http://localhost:3000/api/greet/Alice
# {"message":"Hello, Alice!"}

3. Add a service + dependency injection

Business logic belongs in services. Mark them @Injectable() and the IoC container wires them into controllers automatically. Create src/services/greeter.service.ts:

1
2
3
4
5
6
7
8
import { Injectable } from 'streetjs';

@Injectable()
export class GreeterService {
  greet(name: string): string {
    return `Hello, ${name}! The time is ${new Date().toISOString()}.`;
  }
}

Inject it through the controller constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Controller, Get, Injectable } from 'streetjs';
import type { StreetContext } from 'streetjs';
import { GreeterService } from '../services/greeter.service.js';

@Injectable()
@Controller('/api/greet')
export class GreetingController {
  constructor(private readonly greeter: GreeterService) {}

  @Get('/:name')
  async helloName(ctx: StreetContext): Promise<void> {
    ctx.json({ message: this.greeter.greet(ctx.params['name'] ?? 'stranger') });
  }
}

No manual wiring needed — app.registerController(GreetingController) resolves GreeterService from constructor metadata.

Important — keep import 'reflect-metadata' at the very top of main.ts. The metadata polyfill must load before any decorated class, or DI silently fails. The scaffold already does this for you.


4. Read requests, write responses

StreetContext is your request/response handle:

1
2
3
4
5
6
7
8
9
10
11
12
@Get('/search')
async search(ctx: StreetContext): Promise<void> {
  const q = ctx.query['q'] ?? '';              // ?q=...
  const id = ctx.params['id'];                  // /:id route param
  const auth = ctx.headers['authorization'];    // lowercased header keys
  const body = ctx.body;                        // parsed JSON body

  ctx.json({ q });                              // JSON (default 200)
  // ctx.text('pong');                          // plain text
  // ctx.send(204);                             // empty response
  // ctx.setHeader('X-Request-Id', '...');
}

5. Next steps

Troubleshooting

Symptom Cause / fix
Reflect.getMetadata is not a function import 'reflect-metadata' is missing or not first in main.ts.
Controller routes 404 You forgot app.registerController(...), or the @Controller prefix differs from the URL you call.
Cannot resolve <Service> The service is not @Injectable(), or a circular dependency exists.