Getting started

Install nuxt-roost and write your first decorated controller.
nuxt-roost is a Nuxt module. It targets Nuxt 3 / 4 and runs on its Nitro server. It also works in a standalone Nitro app (no Nuxt) via the bundled Nitro module — see How the codegen works. Requires Node 22+.

Install

pnpm add nuxt-roost
nuxt.config.ts
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:

server/features/projects/projects.repo.ts
// 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) ?? []
  }
  // ...
}
server/features/projects/projects.controller.ts
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.

The feature dir is a flat auto-import namespace: keep exported names unique across features, and avoid names that shadow a TS lib global — so the playground suffixes its entity 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

nuxt.config.ts
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)
  },
})
Copyright © 2026