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

NestJSnuxt-roost
Reads constructor typesat runtime (reflect-metadata + design:paramtypes)at build time (ts-morph AST)
Composition rootbuilt at runtime by the IoC containergenerated as plain asFunction factories
Decorators at runtimeinspectedno-ops
RouterNestJS's ownNitro's file-based router (generated delegates)
Edge bundle weightthe NestJS runtime + reflect-metadatathe 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

NestJSroost
@Controller / @Get / @Postsame
@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 / ValidationPipea 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 @Catch decorator 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.

Copyright © 2026