Getting started
Install
pnpm add nuxt-roost
export default defineNuxtConfig({
modules: ['nuxt-roost'],
})
That's the whole setup — no tsconfig changes needed. roost's decorators are legacy
(experimental) decorators, and the module turns on experimentalDecorators in every generated
tsconfig for you, so controllers typecheck in the editor and under nuxi typecheck out of the box.
Write a feature
Create a feature folder under server/features/ (the name is auto-detected — features/,
nests/, or domain/). A feature is just a few decorated classes:
// Injectable, Scoped, RequestContext — all auto-imported, no import line needed.
@Injectable() // SINGLETON — the shared store / connection pool
export class ProjectStore {
readonly rows = new Map<string, { id: string; name: string }[]>()
}
@Scoped() // tenant-bound, per request (shorthand for @Injectable({ scope: 'scoped' }))
export class ProjectsRepo {
constructor(
private readonly store: ProjectStore,
private readonly ctx: RequestContext,
) {}
all() {
return this.store.rows.get(this.ctx.tenantId) ?? []
}
// ...
}
import { ProjectsService } from './projects.service'
// No `nuxt-roost/runtime` import — Controller, Get, Param, Body (and the rest) are auto-imported.
@Controller() // path defaults to the feature folder → /projects
export class ProjectsController {
constructor(private readonly projects: ProjectsService) {}
@Get()
list() {
return this.projects.list()
}
@Get(':id')
get(@Param() id: string) {
return this.projects.get(id) // route params injected as arguments, Nest-style
}
}
Run nuxt dev. roost runs the codegen, writes the route delegates + the DI manifest into a
gitignored .roost/, and Nitro serves GET /api/projects. Edit a controller and it hot-reloads.
Auto-imports
roost's decorators + types are auto-imported into server code: Controller,
Get/Post/Put/Delete, Injectable/Scoped/Transient,
UseGuards/UseInterceptors/UseFilters, the param-injection decorators
Body/Param/Query/Ctx, the method-level ValidateBody, the pipe helpers
(ParseInt/ParseBool/ParseUUID/DefaultValue/…), the prebuilt HTTP exceptions, and the Ctx /
RequestContext / RoostGuard / RoostInterceptor / RoostFilter types.
Your own feature pieces are auto-imported too — every export under the feature dir (guards,
services, repos, DTO schemas + types) is available without an import, the same way Nuxt
auto-imports server/utils/. So a controller can reference MemberGuard, ProjectsService,
the createProjectSchema zod schema, and the CreateProjectDto type with no import line at all.
ProjectRecord
to keep it distinct from DTOs and helpers. (Provider
class names must already be unique for the DI container, so the main risk is duplicate DTO/helper
names.) On a collision the last one wins. To turn off just the feature-dir auto-imports, set
roost: { autoImportFeatures: false } and import your own pieces explicitly; roost's own
decorators stay auto-imported (disable everything with nitro: { imports: false }).What got generated
Open .roost/manifest.json — a route table with every route's full effective chain:
{
"featureRoot": "server/features",
"routes": [
{
"method": "GET",
"path": "/api/projects",
"handler": "ProjectsController.list",
"guards": [],
"filters": ["defaultErrorFilter (built-in)"],
"body": null
}
]
}
The route delegates (.roost/api/**) and the DI manifest (.roost/di.generated.ts) are real,
openable files — nothing is virtual or hidden.
Module options
export default defineNuxtConfig({
modules: ['nuxt-roost'],
roost: {
featuresDir: 'features', // override auto-detection
globals: { interceptors: ['auditInterceptor'] }, // applied to every route
logRoutes: true, // print the route table on boot
autoImportFeatures: true, // auto-import your feature pieces (default true; false → import them yourself)
},
})