Correctness Rules
20 rules that detect bugs, missing decorators, and runtime errors.
| Rule | Severity | What it catches |
|---|---|---|
no-missing-injectable | error | Provider with constructor deps missing @Injectable() |
no-duplicate-routes | error | Same method + path twice in a controller |
no-missing-guard-method | error | Guard class missing canActivate() |
no-missing-pipe-method | error | Pipe class missing transform() |
no-missing-filter-catch | error | @Catch() class missing catch() |
no-missing-interceptor-method | error | Interceptor missing intercept() |
require-inject-decorator | error | Untyped constructor param without @Inject() |
prefer-readonly-injection | warning | Constructor DI params missing readonly |
require-lifecycle-interface | warning | Lifecycle method without interface |
no-empty-handlers | info | HTTP handler with empty body |
no-async-without-await | warning | Async function with no await |
no-duplicate-module-metadata | warning | Duplicate entries in @Module() arrays |
no-missing-module-decorator | warning | Class named *Module without @Module() |
no-fire-and-forget-async | warning | Async call without await in non-handler methods |
param-decorator-matches-route | error | @Param() name doesn't match any :param in the route path |
factory-inject-matches-params | error | useFactory inject array length mismatches factory parameter count |
no-duplicate-decorators | warning | Same decorator appears twice on a single target |
validated-non-primitive-needs-type | warning | Non-primitive DTO property with class-validator decorators missing @Type() |
validate-nested-array-each | warning | @ValidateNested() on array property missing { each: true } |
injectable-must-be-provided | info | @Injectable() class not registered in any module's providers |
no-missing-injectable
Scope: Project
Detects classes listed in a module's providers array that declare constructor dependencies but are missing the @Injectable() decorator.
Why: Without @Injectable(), NestJS cannot resolve constructor dependencies, causing runtime errors.
// user.service.ts
export class UserService { // Missing @Injectable()
constructor(private readonly db: DatabaseService) {}
}
// user.module.ts
@Module({ providers: [UserService] })
export class UserModule {}
no-duplicate-routes
Detects two handlers with the same HTTP method, path, and version in a single controller.
Why: Duplicate routes produce undefined behavior — only one handler will execute, silently shadowing the other.
@Controller('users')
export class UserController {
@Get(':id')
findOne(@Param('id') id: string) { /* ... */ }
@Get(':id') // Duplicate!
getUser(@Param('id') id: string) { /* ... */ }
}
no-missing-guard-method
Detects guard classes (using @Injectable() or implementing CanActivate) that are missing the canActivate() method. This rule identifies guards by the Guard class name suffix.
Why: A guard without canActivate() will cause a runtime error when NestJS tries to invoke it.
@Injectable()
export class AuthGuard implements CanActivate {
// Missing canActivate()!
}
no-missing-pipe-method
Detects pipe classes missing the transform() method.
Why: Pipes must implement transform() or NestJS will throw at runtime.
@Injectable()
export class ParseIntPipe implements PipeTransform {
// Missing transform()!
}
no-missing-filter-catch
Detects exception filter classes (decorated with @Catch()) missing the catch() method.
Why: Filters must implement catch() or NestJS will throw at runtime.
@Catch(HttpException)
export class HttpFilter implements ExceptionFilter {
// Missing catch()!
}
no-missing-interceptor-method
Detects interceptor classes missing the intercept() method.
Why: Interceptors must implement intercept() or NestJS will throw at runtime.
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
// Missing intercept()!
}
require-inject-decorator
Detects constructor parameters without a type annotation that are also missing @Inject().
Why: Without a type or @Inject(), NestJS cannot resolve the dependency. This causes a runtime error.
@Injectable()
export class UserService {
constructor(private readonly connection) {} // No type, no @Inject()
}
prefer-readonly-injection
Detects constructor DI parameters missing the readonly modifier.
Why: Injected dependencies should not be reassigned. The readonly modifier prevents accidental mutation and communicates intent.
@Injectable()
export class UserService {
constructor(private db: DatabaseService) {}
}
require-lifecycle-interface
Detects lifecycle methods (onModuleInit, onModuleDestroy, etc.) without the corresponding interface.
Why: Without the interface, TypeScript will not catch typos in the method name, and the intent is unclear to readers.
@Injectable()
export class AppService {
onModuleInit() { // No OnModuleInit interface
// ...
}
}
no-empty-handlers
Detects HTTP handler methods with empty bodies.
Why: An empty handler returns undefined, which NestJS serializes as an empty response. This is nearly always unintentional — the handler is either incomplete or should return a value.
@Controller('users')
export class UserController {
@Get()
findAll() {} // Empty body
}
no-async-without-await
Detects async functions or methods that never use await. HTTP handlers with route decorators are exempted, as async is conventional for controller methods.
Why: An async function without await creates an unnecessary promise wrapper. Either remove async or add the missing await.
@Injectable()
export class UserService {
async findAll() {
return this.users; // No await needed
}
}
no-duplicate-module-metadata
Detects duplicate entries in @Module() metadata arrays (imports, providers, controllers, exports).
Why: Duplicate entries are redundant and suggest a copy-paste error. NestJS ignores duplicates, but they obscure the intended module composition.
@Module({
providers: [UserService, AuthService, UserService], // Duplicate!
})
export class UserModule {}
no-missing-module-decorator
Detects classes named *Module that are missing the @Module() decorator.
Why: A class following the module naming convention without @Module() is likely missing the decorator, which means NestJS will not register it.
export class UserModule { // Missing @Module()
// ...
}
no-fire-and-forget-async
Detects async function calls without await in non-handler methods (services, etc.).
Why: Calling an async function without await produces a floating promise. If the promise rejects, the error is silently discarded, leading to data loss or inconsistent state.
@Injectable()
export class OrderService {
complete(orderId: string) {
this.notificationService.sendEmail(orderId); // No await!
this.analyticsService.track('order_completed'); // No await!
}
}
param-decorator-matches-route
Detects @Param() decorator names that don't match any :param segment in the route path (including the controller prefix).
Why: A mismatched @Param() name silently receives undefined at runtime, leading to bugs that are hard to trace.
@Controller('users')
export class UserController {
@Get(':id')
findOne(@Param('userId') userId: string) { // 'userId' doesn't match ':id'
return this.userService.findOne(userId);
}
}
factory-inject-matches-params
Detects useFactory providers where the inject array length doesn't match the factory function's parameter count.
Why: NestJS maps inject tokens to factory parameters by position. A count mismatch means the factory receives wrong or missing values, causing silent bugs or runtime errors.
@Module({
providers: [
{
provide: 'CONFIG',
useFactory: (config) => config.get('db'), // 1 param
inject: [ConfigService, LoggerService], // 2 tokens!
},
],
})
export class AppModule {}
no-duplicate-decorators
Detects the same decorator appearing twice on a single class, method, or property. Stackable decorators like @ApiResponse are allowed.
Why: A duplicate decorator is almost always a copy-paste error. The second occurrence silently overwrites the first or has no effect.
@Controller('users')
export class UserController {
@Get(':id')
@Get(':id') // Duplicate!
findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}
}
validated-non-primitive-needs-type
Detects DTO properties with class-validator decorators on non-primitive types that are missing @Type() from class-transformer.
Why: class-transformer needs @Type() to know which class to instantiate for nested objects. Without it, validation runs against a plain object and nested rules are silently skipped.
export class CreateOrderDto {
@ValidateNested()
address: AddressDto; // Missing @Type()!
}
validate-nested-array-each
Detects @ValidateNested() on array-typed properties that are missing { each: true }.
Why: Without { each: true }, @ValidateNested() validates the array object itself instead of each element, so invalid items pass validation silently.
export class CreateOrderDto {
@ValidateNested() // Missing { each: true }!
@Type(() => ItemDto)
items: ItemDto[];
}
injectable-must-be-provided
Scope: Project
Detects @Injectable() classes that are not registered in any module's providers array. Infrastructure classes (guards, pipes, filters, interceptors, etc.) are skipped.
Why: An @Injectable() class that isn't registered anywhere is either dead code or a forgotten registration — NestJS will throw if you try to inject it.
@Injectable()
export class UsersService { // Not in any module's providers!
findAll() { /* ... */ }
}