Skip to content

Tags and Security

Tags group related endpoints in the generated spec and docs UI. Security schemes declare the authentication methods your API supports. Both can be set at the controller level and refined at the route level.

Pass a tags array in the @Controller options to tag every route in that controller:

@Controller('/api/users', { tags: ['Users'] })
export class UsersController implements IController {
// All routes in this controller are tagged with "Users"
}

Add tags to individual routes with the tags property on @Route. Route-level tags are appended to the controller-level tags:

@Controller('/api/users', { tags: ['Users'] })
export class UsersController implements IController {
@Route({
response: userListSchema,
tags: ['Admin'],
summary: 'List all users',
})
index(ctx: RouterContext) {
return ctx.json({ data: [] })
}
}

In this example, the index route has both the Users and Admin tags. Other routes on the controller that do not specify their own tags will only have Users.

Stratal includes three built-in security schemes that are always available in the generated spec:

Scheme nameOpenAPI typeLocationDetails
bearerAuthHTTP bearerAuthorization headerBearer format: JWT
apiKeyAPI keyX-API-Key headerHeader-based API key
sessionCookieAPI keysession cookieCookie-based session

These schemes are defined under components.securitySchemes in the generated spec. You reference them by name in your controller and route options.

Pass a security array in the @Controller options. Every route in the controller inherits these schemes:

@Controller('/api/users', {
tags: ['Users'],
security: ['bearerAuth'],
})
export class UsersController implements IController {
// All routes require bearerAuth
}

Add a security array on @Route to extend the controller-level security. Route security is merged with controller security:

@Controller('/api/users', {
tags: ['Users'],
security: ['bearerAuth'],
})
export class UsersController implements IController {
@Route({
response: userListSchema,
security: ['apiKey'],
summary: 'List all users',
})
index(ctx: RouterContext) {
// This route requires both bearerAuth and apiKey
return ctx.json({ data: [] })
}
}

To mark a route as requiring no authentication, pass an empty security array. Since route security is merged with controller security, an empty array on its own does not remove the controller’s schemes. To make a truly public route, the controller should not have security set, or the route should be on a controller without security:

@Controller('/api/health')
export class HealthController implements IController {
@Route({
response: z.object({ status: z.string() }),
security: [],
summary: 'Health check',
})
index(ctx: RouterContext) {
return ctx.json({ status: 'ok' })
}
}

When a route or its controller has guards (via @UseGuards), Stratal automatically adds sessionCookie to the security array if it is not already present. This reflects the fact that guarded routes require authentication:

@Controller('/api/admin', {
tags: ['Admin'],
security: ['bearerAuth'],
})
@UseGuards(AuthGuard)
export class AdminController implements IController {
@Route({
response: adminDashboardSchema,
summary: 'Admin dashboard',
})
index(ctx: RouterContext) {
// Security in the spec: bearerAuth + sessionCookie (auto-added)
return ctx.json({ data: {} })
}
}

To define security schemes beyond the three built-in ones, pass a securitySchemes object in OpenAPIModule.forRoot():

OpenAPIModule.forRoot({
info: { title: 'My API', version: '1.0.0' },
securitySchemes: {
oauth2: {
type: 'oauth2',
flows: {
authorizationCode: {
authorizationUrl: 'https://auth.example.com/authorize',
tokenUrl: 'https://auth.example.com/token',
scopes: {
'read:users': 'Read user data',
'write:users': 'Modify user data',
},
},
},
},
},
})

You can then reference the custom scheme name in your controller and route security arrays.

See Hiding Routes to learn how to exclude internal or work-in-progress endpoints from the generated spec.