Grouping & Versioning
Each controller carries its own path prefix via @Route. For a base path shared across many controllers — most often an API version segment like /v1 — register them under a group instead of repeating the prefix on every class. The base path is reflected in both the mounted Express routes and the generated OpenAPI paths.
A base path per controller
Pass a prefix when registering. It is prepended to the controller's own @Route prefix:
api.register(new UsersController(svc), { prefix: '/v1' });
// @Route('users') + '/v1' → /v1/usersA group of controllers
group(prefix, fn) opens a scope; every controller registered on it inherits the base path:
api.group('/v1', (v1) => {
v1.register(new UsersController(svc));
v1.register(new AuthController(auth));
});
// → /v1/users, /v1/auth/...Groups nest, and a per-register prefix appends to the enclosing group:
api.group('/v1', (v1) => {
v1.group('/admin', (admin) => {
admin.register(new ReportsController(svc)); // → /v1/admin/reports
});
v1.register(new UsersController(svc), { prefix: '/internal' }); // → /v1/internal/users
});Running two versions side by side
Because registration is just recording an instance under a prefix, the same controller class can be mounted under more than one version — or you can register a new version's controller alongside the old one:
api.group('/v1', (v1) => v1.register(new UsersController(svc)));
api.group('/v2', (v2) => v2.register(new UsersControllerV2(svc)));Each produces its own entry in the spec (/v1/users, /v2/users), so clients generated from the document see both versions.
Unversioned infra routes
Mount operational endpoints that shouldn't be versioned (a /swagger.json, a /docs UI, a load-balancer /healthz) directly on the Express app rather than inside a group — only covenix controllers pick up the group prefix.
Static generation
To keep static, instance-free generation identical to api.swagger() when you use groups, wrap each class with its prefix:
import { generateSwagger } from 'covenix';
const doc = generateSwagger([
{ controller: UsersController, prefix: '/v1' },
{ controller: AuthController, prefix: '/v1' },
]);A bare class (no wrapper) still works and carries no base path, so you can mix the two forms.
