npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nestjs-module-diagram

v0.0.5

Published

A NestJS Module to do a deep dive through the dependency tree structure and print the dependencies. (I change peerDependency)

Downloads

6

Readme

NestJS-Module-Diagram

Description

This module does a bit of a dive through the provided module and reads through the dependency tree from the point of entry given. It will find what a module imports, provides, has controllers for, and exports and will recursively search through the dependency tree until all modules have been scanned. For providers if there is a custom provider, the Diagram will do its best to determine if Nest is to use a value, a class/standard, or a factory, and if a factory, what value is to be injected.

Installation

Pretty straightforward installation:

npm i nestjs-module-diagram
yarn add nestjs-module-diagram
pnpm add nestjs-module-diagram

Exploration Mode

Exploration Usage

Much like the SwaggerModule, the DiagramModule is not a module that you register within Nest's DI system, but rather use after the DI system has done all the heavy lifting. Simple usage of the Diagram could be like:

// ...
import { DiagramModule } from 'nestjs-module-diagram'; 

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // const app = await NestFactory.createApplicationContext(AppModule);
  console.log(DiagramModule.explore(app));
  // ...
}
// ...

The DiagramModule will not get in the way of application bootstrapping, and will still allow for the server to listen.

Excluding modules

DiagramModule.explore(app, {
  // A list of regexes or predicate functions to apply over modules that will be ignored
  ignoreImports: [
    /^TypeOrmModule/i,
    (moduleName) => moduleName.endsWith('something'),
  ],
})

Exploration Sample Output

Given the following source code

// main.ts
import * as util from 'util'
import { NestFactory } from '@nestjs/core'
import { DiagramModule } from 'nestjs-module-diagram'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.createApplicationContext(AppModule, { logger: false })
  console.log(
    util.inspect( DiagramModule.explore(app), { depth: Infinity, colors: true } )
  )
}
bootstrap();

// src/app.module.ts
import { Module, Injectable, Controller } from '@nestjs/common'

@Controller('hamsters')
export class HamstersController {}
@Injectable()
export class HamstersService {}

@Module({
  controllers: [HamstersController],
  providers: [HamstersService],
})
export class HamstersModule {}


@Controller('dogs')
export class DogsController {}
export class DogsService {}

@Module({
  controllers: [DogsController],
  providers: [
    {
      provide: DogsService,
      inject: ['someString'],
      useFactory: (str: string) => new DogsService(),
    },
    {
      provide: 'someString',
      useValue: 'my string',
    },
  ],
  exports: [DogsService],
})
export class DogsModule {}


@Controller('cats')
export class CatsController {}
@Injectable()
export class CatsService {}

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}


export class AnimalsService {}
@Controller('animals')
export class AnimalsController {}

@Module({
  imports: [CatsModule, DogsModule, HamstersModule],
  controllers: [AnimalsController],
  providers: [
    {
      provide: AnimalsService,
      useValue: new AnimalsService(),
    }
  ],
  exports: [DogsModule],
})
export class AnimalsModule {}


@Module({
  imports: [AnimalsModule],
})
export class AppModule {}

it outputs this:

[
  {
    name: 'AppModule',
    imports: [ 'AnimalsModule' ],
    providers: {},
    controllers: [],
    exports: []
  },
  {
    name: 'AnimalsModule',
    imports: [ 'CatsModule', 'DogsModule', 'HamstersModule' ],
    providers: { AnimalsService: { method: 'value' } },
    controllers: [ 'AnimalsController' ],
    exports: [ 'DogsModule' ]
  },
  {
    name: 'CatsModule',
    imports: [],
    providers: { CatsService: { method: 'standard' } },
    controllers: [ 'CatsController' ],
    exports: []
  },
  {
    name: 'DogsModule',
    imports: [],
    providers: {
      DogsService: { method: 'factory', injections: [ 'someString' ] },
      someString: { method: 'value' }
    },
    controllers: [ 'DogsController' ],
    exports: [ 'DogsService' ]
  },
  {
    name: 'HamstersModule',
    imports: [],
    providers: { HamstersService: { method: 'standard' } },
    controllers: [ 'HamstersController' ],
    exports: []
  }
]

In this example, AppModule imports AnimalsModule, and AnimalsModule imports CatsModule, DogsModule, and HamstersModule and each of those has its own set of providers and controllers.

Graph Mode

Sometimes you want to visualize the module interdependencies, so you can better reason about them. The DiagramModule has a graph method that builds on the output of the explore method by generating a doubly-linked graph where each node represents a module and each edge a link to that module's dependencies or dependents. The getEdges method can traverse this graph from the root (or any given) node, recursively following dependencies and returning a flat array of edges. These edges can be easily mapped to inputs for graphing tools, such as Mermaid.

Graphing Usage

Assume you have the sample output of the above explore section in a variable called tree. The following code will generate the list of edges suitable for pasting into a Mermaid graph.

Create src/graph.ts file:

import * as fs from 'fs-extra';

async function bootstrap(): Promise<void> {
    const app: INestApplication = await NestFactory.create(AppModule);
    const tree = DiagramModule.explore(app);
    const root = DiagramModule.graph(tree);
    const edges = DiagramModule.findGraphEdges(root);
    const mermaidEdges = edges.map(
        ({ from, to }) => `  ${from.module.name}-->${to.module.name}`,
    );
    fs.writeFileSync('graph.md', '```mermaid\ngraph LR\n' + mermaidEdges.join('\n') + '\n```');
    app.close();
    console.log(`\x1b[44m GRAPH \x1b[0m\x1b[34m Graph of module for review is already generated at graph.md !\x1b[0m`);
}

bootstrap().then();

Add script in your package.json:

{
  "scripts": {
    "graph": "nest start --entryFile graph"
  }
}

Run command:

npm run graph
# OR
yarn graph
# OR
pnpm graph

Check content of graph.md file at your root project:

graph LR
  AppModule-->CommonModule
  CommonModule-->ConfigModule
  ConfigModule-->ConfigHostModule
  ....
  FeatureModule-->TypeOrmCoreModule
  AppModule-->ConfigHostModule
  AppModule-->TypeOrmCoreModule

You can use the result at: https://mermaid.live/edit (Do not copy line 1 and last line).

If you use vscode, just install Markdown Preview Mermaid Support extension:

Open graph.md file and use this:

And enjoy your result:

The edges can certainly be transformed into formats more suitable for other visualization tools. And the graph can be traversed with other strategies.

Debug Mode

Every now again you may find yourself running into problems where Nest can't resolve a provider's dependencies. The DiagramModule has a debug method that's meant to help out with this kind of situation.

Debug Usage

Assume you have a DogsModule with the following information:

@Module({
  controller: [DogsController],
  exports: [DogsService],
  providers: [
    {
      provide: 'someString',
      useValue: 'something',
    },
    {
      provide: DogsService,
      inject: ['someString'],
      useFactory: (someStringInjection: string) => {
        return new DogsService(someStringInjection)
      },
    }
  ]
})
export class DogsModule {}

Now the DiagramModule.debug() method can be used anywhere with the DogsModule to get the dependency tree of the DogsModule including what the controller depends on, what imports are made, and what providers exist and their token dependencies.

async function bootstrap() {
  const dogsDeps = await DiagramModule.debug(DogsModule);
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}

Because this method does not require the INestApplicationContext it can be used before the NestFactory allowing you to have insight into what is being seen as the injection values and what's needed for the module to run.

Debug Sample Output

The output of the debug() method is an array of metadata, imports, controllers, exports, and providers. The DogsModule from above would look like this:

[
  {
    name: 'DogsModule',
    imports: [],
    providers: [
      {
        name: 'someString',
        dependencies: [],
        type: 'value',
      },
      {
        name: 'DogsService',
        dependencies: ['someString'],
        type: 'factory',
      },
    ],
    controllers: [
      {
        name: 'DogsController',
        dependencies: ['DogsService'],
      },
    ],
    exports: [
      {
        name: 'DogsService',
        type: 'provider',
      },
    ],
  },
];

Debug Messages

If you are using the debug method and happen to have an invalid circular, the DiagramModule will write message to the log about the possibility of an unmarked circular dependency, meaning a missing forwardRef and the output will have ***** in place of the imports where there's a problem reading the imported module.