DEV Community

Ziv
Ziv

Posted on

NestJS Builtin Anti-Pattern

It all starts with Angular. The modular router API contained the following static methods:

class RouterModule {
   static forRoot(...);
   static forFeature(...);
}
Enter fullscreen mode Exit fullscreen mode

This is a very strange way to configure something, and the Angular team probably noticed that because the new functional standalone API does not have this distinguish between types of routes.

import { provideRouter } from '@angular/router';

// root or feature are the same from now on
provideRouter([]);
Enter fullscreen mode Exit fullscreen mode

Why?

Because modules are not configurable entities.

A module is a logical container for classes, functions, and constants in programming. Since there are no module instances, there is nothing to configure (tc39 specs, MDN about modules).

In modern Javascript, each file with an export statement is a module.

// foo.js
// Hooray, I'm a module
export const FOO = true;
Enter fullscreen mode Exit fullscreen mode

Let’s examine Javascript modules:

  • Module run once! (first import)
  • Module imports its dependencies using import statements
  • Module exports selected defined or imported elements

Importing this file will not make it run every time. It will be executed once, and each import will get the same reference to the module exports.

// module.js
// importing dependencies
import { SuperClient } from 'super-lib'; // importing module by name
import { SuperEnum } from './consts.js'; // importing module by path

// internal module function (no export)
const =  check = () => false;

// exported function
export function createSuperClient(type: SuperEnum) {
}

// exported imported element
export { SuperClient };

Enter fullscreen mode Exit fullscreen mode

NestJS uses the same mechanism as Javascript modules.

@Module({
  // The same as the "import" statement in a Javascript module
  imports: [OtherNestModule],

  //The content of this module
  providers: [MyService],
  controllers: [],

  // The same as the "export" statement in a Javascript module
  // We choose what we want to export (expose)
  exports: [MyService, OtherNestModule],
})
class SuperModule {}

Enter fullscreen mode Exit fullscreen mode

What’s the problem?

NestJS’s documentation shows how to use the configuration module, an official NestJS module (repository of the NestJS).

The module exposes a familiar static method “forRoot()”. Someone familiar with Angular thought providing this function to “configure” the module would be a good idea.

But let’s remember that we are not configuring the module — we are configuring the service exposed by this module. The action of “configuring” a module is the same as making all its content singletons. There is no way to create multiple instances of the ConfigurationService with different values. The service is bound to the “configuration” passed to the module.

This configuration module was the first, but more come after even worse! New NestJS modules introduce two API calls to “configure” a module. The mighty “forRoot()” and his shiny friend “forRootAsync()”.
(A sneak pick of the implementation shows that the “forRoot()” calls the “forRootAsync(),” so… why do we need 2 API calls that do the same thing?!
What is wrong with one API that rules them both?!)

Solution

Stop trying to “configure” modules and start configuring your services.

Top comments (0)