nestjs-doctorGitHub

Correctness Rules

14 rules that detect bugs, missing decorators, and runtime errors.

RuleSeverityWhat it catches
no-missing-injectableerrorProvider in module missing @Injectable()
no-duplicate-routeserrorSame method + path twice in a controller
no-missing-guard-methoderrorGuard class missing canActivate()
no-missing-pipe-methoderrorPipe class missing transform()
no-missing-filter-catcherror@Catch() class missing catch()
no-missing-interceptor-methoderrorInterceptor missing intercept()
require-inject-decoratorerrorUntyped constructor param without @Inject()
prefer-readonly-injectionwarningConstructor DI params missing readonly
require-lifecycle-interfacewarningLifecycle method without interface
no-empty-handlerswarningHTTP handler with empty body
no-async-without-awaitwarningAsync function with no await
no-duplicate-module-metadatawarningDuplicate entries in @Module() arrays
no-missing-module-decoratorwarningClass named *Module without @Module()
no-fire-and-forget-asyncwarningAsync call without await in non-handler methods

no-missing-injectable

Scope: Project

Detects classes listed in a module's providers array that are missing the @Injectable() decorator.

Why: Without @Injectable(), NestJS cannot resolve the class's 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.

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.

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!
  }
}