Skip to content

Quickstart

This walks through the smallest possible Blixis app: one service, one controller, one module, running on a real socket. It mirrors the shape of the framework’s own examples/hello-api reference app — see the hello-api Walkthrough for the full CRUD version.

Terminal window
mkdir hello-quickstart && cd hello-quickstart
pnpm init
pnpm add @blixis-io/core @blixis-io/di @blixis-io/http zod
pnpm add -D typescript @types/node
mkdir src

In package.json, add "type": "module" and these scripts:

package.json
{
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es2023",
"lib": ["es2023"],
"types": ["node"],
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

Note there’s no tsx/ts-node step here: Node’s own TypeScript support strips types but doesn’t transform decorators (they’re runtime JavaScript, not type syntax), so every app in this framework builds with tsc first and runs the compiled output. See Installation for why the two decorator compiler flags are non-negotiable.

src/hello.service.ts
import { Injectable } from "@blixis-io/di";
@Injectable()
export class HelloService {
greet(name: string): string {
return `Hello, ${name}!`;
}
}

@Injectable() isn’t optional decoration — TypeScript only emits the constructor’s parameter types (design:paramtypes) for a decorated class, so it’s what makes this class resolvable at all. See Dependency Injection.

src/hello.controller.ts
import { Controller, Get, Param } from "@blixis-io/http";
import { HelloService } from "./hello.service.js";
@Controller("hello")
export class HelloController {
constructor(private readonly hello: HelloService) {}
@Get(":name")
greet(@Param("name") name: string) {
return { message: this.hello.greet(name) };
}
}

this.hello was never assigned by hand — the container built it from HelloService’s own @Injectable() registration. See Routing & Controllers.

src/app.module.ts
import { Module } from "@blixis-io/core";
import { HelloController } from "./hello.controller.js";
import { HelloService } from "./hello.service.js";
@Module({
providers: [HelloService],
controllers: [HelloController],
})
export class AppModule {}
src/main.ts
import { createHttpApplication } from "@blixis-io/http";
import { AppModule } from "./app.module.js";
const app = await createHttpApplication(AppModule);
await app.listen(3000);
console.log("Listening on http://localhost:3000");
Terminal window
pnpm run build && pnpm start
Terminal window
curl http://localhost:3000/hello/world
{ "message": "Hello, world!" }
  • createHttpApplication(AppModule) walked the module graph, registered HelloService and HelloController in a Container, resolved everything, and ran any OnModuleInit hooks (none here).
  • app.listen(3000) bound a real node:http server and translated every incoming request into a Web-standard Request, then translated the returned Response back onto the socket.
  • @Get(":name") on HelloController combined with the controller’s "hello" prefix to register GET /hello/:name on the internal router; @Param("name") pulled the :name segment out of the matched route and handed it to greet() as a plain string.