vs. NestJS
What's the same, what's deliberately different, and what roost is not.
roost borrows NestJS's authoring model — decorated controllers, DI, guards/interceptors/filters — and almost nothing of its runtime. It's a Nitro-native take, not a port.
The big difference: no runtime reflection
| NestJS | nuxt-roost | |
|---|---|---|
| Reads constructor types | at runtime (reflect-metadata + design:paramtypes) | at build time (ts-morph AST) |
| Composition root | built at runtime by the IoC container | generated as plain asFunction factories |
| Decorators at runtime | inspected | no-ops |
| Router | NestJS's own | Nitro's file-based router (generated delegates) |
| Edge bundle weight | the NestJS runtime + reflect-metadata | the runtime only — ts-morph never ships |
NestJS's reflection is powerful but couples you to its runtime and a metadata polyfill. roost moves the same information to build time, so the edge story is "just Nitro."
What maps over
| NestJS | roost |
|---|---|
@Controller / @Get / @Post | same |
@Injectable() + constructor injection, lifetimes via { scope } | same — plus @Scoped() / @Transient() shortcuts (@Injectable() → SINGLETON, @Controller → SCOPED) |
@UseGuards (class + method) | same — DI'd guards, merged |
@UseInterceptors (intercept/next) | same — DI'd interceptors |
| Exception filters | @UseFilters — DI'd, self-inspecting (no @Catch(Type) yet) |
Built-in HTTP exceptions (NotFoundException, ForbiddenException, …) | same names, extend HttpError; mapped by the built-in fallback (over h3, not HttpException) |
@Body() / @Param() / @Query() / @Req() param injection | @Body(schema) / @Param('id', schema?) / @Query(schema) / @Ctx — same shape; the schema/pipe rides on the decorator (no reflect-metadata to read the type) |
Pipes — ParseIntPipe / ParseBoolPipe / ParseUUIDPipe / DefaultValuePipe / ParseArrayPipe / ParseEnumPipe / ValidationPipe | a zod schema is the pipe: ParseInt / ParseBool / ParseUUID / DefaultValue / ParseArray / ParseEnum helpers (auto-imported), on @Param('id', …) / @Query('key', …) / @Body. One chained schema instead of a pipe list |
Global guards/interceptors/filters (APP_*) | roost.globals config |
What's different on purpose
- Filters self-inspect rather than using a
@Catch(Type)registry — DI without a matching engine. A@Catchdecorator is a planned level-2 add. - Cross-cutting concerns reference tokens, resolved per request, instead of provider instances threaded through a module system.
@Body()carries the schema, and a bare@Body()is a build error. Nest's bare@Body()injects and a global pipe validates — two jobs in one decorator. roost can't reflect the type into a validator, so the schema is explicit (@Body(schema)); requiring it means injection never silently skips validation.@Body(null)is the explicit "raw, no validation" escape hatch.- No
@Module(yet) — features are folders; the composition root is generated whole.
What roost is not
It's not a full framework. There's no built-in @Module graph, no microservices transport, no
GraphQL layer. It's an ergonomics layer over Nitro + awilix: the same structure you'd build by
hand (see before / after), generated from decorators instead of hand-maintained.
If you need NestJS's full runtime, use NestJS. If you're on Nitro/Nuxt and want the authoring nicety
without the reflection, that's roost.