DEV Community

Cover image for 2. Why do we add @Module() on the Nest.js app module?
Ivad Yves HABIMANA
Ivad Yves HABIMANA

Posted on

2. Why do we add @Module() on the Nest.js app module?

This article is part of the Nest.js Deep Dive Series. I recommend reading the previous article first to make following along easier.
You can find the code described in the article HERE



In the previous article, we introduced Nest.js, explained what it is, and created a simple Nest application in JavaScript.

Here is the code we currently have

import { NestFactory } from "@nestjs/core";

class AppModule {} // Just an empty class for now

const bootstrapp = async () => {
  const app = await NestFactory.create(AppModule); // we create the app with our module
  await app.listen(3000);
};

bootstrapp();
Enter fullscreen mode Exit fullscreen mode

At this point, we are calling await NestFactory.create() and passing in AppModule to create our Nest.js application. The AppModule is currently just an empty class, so the code runs but doesn’t actually do anything.

In this article, we’ll take a closer look at what the AppModule is and how it’s supposed to work.

Nest.js Modules

According to the official Nest documentation

A module is a class that is annotated with the @Module() decorator. This decorator provides metadata that Nest uses to efficiently organize and manage the application structure.

But what does this really mean? Let’s break it down into simpler terms.

First, a module is a class
In the previous article, we initialized a Nest application by calling the NestFactory.create() function. This function expects a Nest.js module, which is why we were able to pass an empty class—it worked because, at its core, a module is a class.

However, a module is not just any class. The definition says, “A module is a class that is annotated with the @Module() decorator.”

This a@Module() decorator is what makes it special. It adds extra metadata that Nest internally uses to organize and manage the application structure.

To really understand this, we first need to understand what a decorator is.

What is a decorator?

At a high level, a decorator is a function that wraps another function, class, or method to add extra behavior to it, without modifying its original code.

Decorator demonstration
source: https://www.telerik.com/blogs/decorators-in-javascript

Let's take a real-life example

We decorate things because we want them to look or behave differently. For example, you might take a plain box and wrap it in gift paper to turn it into a Christmas present. 🎁
In this case, the box hasn’t changed; it’s just been decorated to serve a new purpose as a gift container.

Another example is when someone uses lipstick for makeup; they are decorating their lips to make them have a different look

Notice that by decorating something, we don’t change its original structure. This gives us flexibility—we can apply or remove decorations as we like. Lipstick, for example, doesn’t permanently alter someone’s lips (it would be weird if they did 😁); they can always apply a different makeup style later.

Now let’s see this concept in code

class Box {
  constructor() {
    this.color = "white";
  }
}
Enter fullscreen mode Exit fullscreen mode

We define a box that has a white color

function GiftContainer(color){
  return function (target){
    target.coverColor = color
  } 
}
Enter fullscreen mode Exit fullscreen mode

We define a decorator GiftContainer that takes a cover color and applies it to the given "target" class

Here is the full code example

function GiftContainer(color) {
  return function (target) {
    target.coverColor = color;
  };
}

@GiftContainer("red")
class Box {
  constructor() {
    this.color = "white";
  }
}

const birthdayBox = new Box();

console.log(birthdayBox.coverColor); // Outputs "red"
Enter fullscreen mode Exit fullscreen mode

We use the @ syntax to apply the decorator. It calls the GiftContainer function and passes the Box class as its target.

The real power of decorators comes from separating enhancement logic from the original implementation.

In Nest.js, for example, you might have the following code:

@Controller('')
class UserController(...)
Enter fullscreen mode Exit fullscreen mode

Depending on how @Controller is implemented, applying it to UserController adds extra information and behavior that Nest can recognize internally to make your application work.

Using Decorators in our JavaScript Nest.js application

Now that we have a basic understanding of how decorator works, let’s integrate them into our Nest.js app from the previous article.

Note:
Currently, JavaScript doesn’t officially support decorator syntax—it’s still considered experimental. However, we can use Babel, a JavaScript transpiler, to enable decorators even before they become a standard feature.

Let's install the necessary Babel dependencies

npm install @babel/core @babel/cli @babel/plugin-proposal-decorators
Enter fullscreen mode Exit fullscreen mode
  • @babel/core includes the base functionality that Babel needs to work

  • @babel/cli: Allow us to use Babel via command line (terminal)

  • @babel/plugin-proposal-decorators: Now the package that will allow us to use decorators.

After installing, we need to configure Babel so it recognizes our setup.

  • Create a file called babel.config.json paste the following configuration
// babel.config.json

{
  "plugins": [["@babel/plugin-proposal-decorators", { "version": "2023-05" }]]
}
Enter fullscreen mode Exit fullscreen mode

This tells Babel that we’re using the decorators plugin (the one we just installed) and specifies the version we’re targeting (the latest one as of now)

Next, we need to update or start the script so Babel compiles our code before Node runs it:

// package.json

"scripts": {
  "start": "babel index.js -d dist && node dist/index.js"
}
Enter fullscreen mode Exit fullscreen mode

Now our start script first runs Babel to compile index.js and output the result in a dist folder. Once that’s done, Node runs the compiled version (dist/index.js).

Now if you run npm start, our app should work as before, but now with Babel in place to support decorators.

Back to Our Module

Let’s revisit the definition of a module:

A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.

With our new understanding of decorators, we can guess what @Module() is doing; it wraps a class and attaches metadata that Nest uses internally to link different parts of the app together.

With our new knowledge of decorator, we can have an idea of what @Module() must be doing, it will wrap a class and provide some metadata that Nest will use internally to know which parts of the application are related. Let’s see this in action

We updated our application to be like the following

import { Module } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";

@Module({})
class AppModule {}

const bootstrapp = async () => {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
};

bootstrapp();
Enter fullscreen mode Exit fullscreen mode

Here, we imported the @Module decorator and applied it to our AppModule class.
We’re passing an empty object for now, since @Module() expects metadata—such as which controllers or providers belong to this module—but we haven’t added any yet.

You can have multiple modules in a Nest.js application, but you need at least one root module to initialize the app. The module passed into NestFactory.create() is known as the root module.

Now, if you run npm start again, your Nest application should start up just as before—only this time, it’s using proper module structure.

Top comments (0)