Lint files and dependencies in NestJs with unimported

Reading Time: 5 minutes

Loading

Introduction

When developers maintain a NestJS project, they add and delete codes to the project that some dependencies and files become redundant. When unused files and dependencies are left around, they become technical debts and reduce the code quality. Fortunately, we can use unimported to lint files and dependencies before git commit to avoid pushing unused files and dependencies to the code base.

In this blog post, I describe how to execute unimported binary to add .unimportedrc.json to a Nestjs project and create a pre-commit hook to lint files and dependencies of the projects during git commit.

let's go

Create a new Nestjs project

nest generate test-app

Add .unimportedrc.json to lint files and dependencies

First, we execute unimported binary to initialize .unimportedrc.json to the project

npx unimported --init

We will find a barebone .unimportedrc.json after the initialization.

{
  "ignorePatterns": [
    "**/node_modules/**",
    "**/*.tests.{js,jsx,ts,tsx}",
    "**/*.test.{js,jsx,ts,tsx}",
    "**/*.spec.{js,jsx,ts,tsx}",
    "**/tests/**",
    "**/__tests__/**",
    "**/*.d.ts"
  ],
  "ignoreUnimported": [],
  "ignoreUnused": [],
  "ignoreUnresolved": []
}

Most of the Nestjs projects that I work on use TypeOrm to generate migration scripts and interface with PostgreSQL; therefore, I want unimported to ignore **/migrations/** folder in ignorePatterns array.

{
  "ignorePatterns": [
    "**/node_modules/**",
    "**/*.tests.{js,jsx,ts,tsx}",
    "**/*.test.{js,jsx,ts,tsx}",
    "**/*.spec.{js,jsx,ts,tsx}",
    "**/tests/**",
    "**/__tests__/**",
    "**/*.d.ts",
    "**/migrations/**"
  ],
  "ignoreUnimported": [],
  "ignoreUnused": [],
  "ignoreUnresolved": []
}

NestJs is nodejs under the hood and unimported also supports a node preset.

npx unimported --show-preset        // show all available presets
npx unimported --show-preset node   // shows the json file of node preset

The following is the output of the configuration:

{
  preset: 'node',
  entry: [ 'src/main.ts' ],
  extensions: [ '.js', '.jsx', '.ts', '.tsx' ],
  flow: false,
  ignorePatterns: [
    '**/node_modules/**',
    '**/*.tests.{js,jsx,ts,tsx}',
    '**/*.test.{js,jsx,ts,tsx}',
    '**/*.spec.{js,jsx,ts,tsx}',
    '**/tests/**',
    '**/__tests__/**',
    '**/*.d.ts'
  ],
  ignoreUnimported: [],
  ignoreUnresolved: [],
  ignoreUnused: []
}

We copy preset, entry and extensions to the config file in the project and the configuration file finally completes.

{
  "preset": "node",
  "entry": [
    "src/main.ts"
  ],
  "extensions": [
    ".js",
    ".jsx",
    ".ts",
    ".tsx"
  ],
  "ignorePatterns": [
    "**/node_modules/**",
    "**/*.tests.{js,jsx,ts,tsx}",
    "**/*.test.{js,jsx,ts,tsx}",
    "**/*.spec.{js,jsx,ts,tsx}",
    "**/tests/**",
    "**/__tests__/**",
    "**/*.d.ts",
    "**/migrations/**"
  ],
  "ignoreUnimported": [],
  "ignoreUnused": [],
  "ignoreUnresolved": []
}

Create pre-commit hook to perform unimported check

First, we install husky devDependencies and add prepare script in package.json.

npm install husky --save-dev --save-exact
npm set-script prepare "husky install"
npm run prepare

Then, we add a pre-commit husky hook

npx husky add .husky/pre-commit "npx --no unimported"
git add .husky/pre-commit
git commit -m "chore: test unimported check"

If we see the follow output in the terminal, pre-commit hook has successfully performed unimported check.

✓ There don't seem to be any unimported files.
[master f33fe57] chore: test unimported check
 1 file changed, 2 insertions(+), 1 deletion(-)

Up to this point, we focus on setting up code quality tools for the project. Now, we are ready to demonstrate the power of unimported by adding unused file(s) and dependencies. In the demo, husky hook aborts whenever we commit the source codes and unimported prints the report card in the terminal. Then, we systematically modify the codes until we satisfy unimported check and commit the changes.

Demo unimported to lint files and dependencies

The demo is to add configuration and .env validation in Nestjs according to the official documentation https://docs.nestjs.com/techniques/configuration.

First, install dependencies

npm i --save-exact @nestjs/config dotenv joi
git add .
git commit -m "chore(deps): install dependencies"
       summary               unimported v1.21.0 (node)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       entry file          : src/main.ts

       unresolved imports  : 0
       unused dependencies : 3
       unimported files    : 0

─────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      │ 3 unused dependencies
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1  │ @nestjs/config
   2  │ dotenv
   3  │ joi
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Inspect the results and run npx unimported -u to update ignore lists
husky - pre-commit hook exited with code 1 (error)

We have 3 unused dependencies but @nestjs/config will go away when we import ConfigModule in AppModule.

import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
git add .
git commit -m "chore(deps): install dependencies"

       summary               unimported v1.21.0 (node)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       entry file          : src/main.ts

       unresolved imports  : 0
       unused dependencies : 2
       unimported files    : 0

─────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      │ 2 unused dependencies
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1  │ dotenv
   2  │ joi
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

We have resolved 1 unused dependency and 2 remain. Next, we use joi to validate the schema of .env.

Let’s create a new file to define the schema of .env file

touch src/validation-schema.ts

Then, we copy and paste the schema to the TypeScript file

// validation-schema.ts
import * as Joi from 'joi';

export const validationSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test', 'provision')
    .default('development'),
  PORT: Joi.number().default(3000),
});

If we commit the codes now, unimported will detect one unused file and 2 unused dependencies.

       summary               unimported v1.21.0 (node)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       entry file          : src/main.ts

       unresolved imports  : 0
       unused dependencies : 2
       unimported files    : 1

─────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      │ 2 unused dependencies
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1  │ dotenv
   2  │ joi
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

─────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      │ 1 unimported files
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1  │ src/validation-schema.ts
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

It seems umimported report card is worse than before but it is alright. We forgot to import validation-schema.ts in app.module.ts and pass the validationSchema object to ConfigModule.

Update app.module.ts and commit again

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { validationSchema } from './validation-schema';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

       summary               unimported v1.21.0 (node)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       entry file          : src/main.ts

       unresolved imports  : 0
       unused dependencies : 1
       unimported files    : 0

─────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      │ 1 unused dependencies
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1  │ dotenv
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Why is dotenv unused? I referred to package.json of @nestjs/config and found out that dotenv is a dependency of the library. I don’t require to install dotenv in the project and should remove it.

We can make it happen by running npm uninstall dotenv. Let’s commit the codes and not keep the suspense further.

git add .
connie@connie-ThinkPad-T450s ~/Documents/ws_react_graphql_nest/test-app $ git commit -m "chore(deps): install dependencies and add configuration"
✓ There don't seem to be any unimported files.
[redo-demo 44cbf8d] chore(deps): install dependencies and add configuration
 5 files changed, 6 insertions(+), 7 deletions(-)
 rename src/{validationSchema.ts => validation-schema.ts} (100%)

We have finally committed the changes and remove dotenv dependency that the project does not require.

Final Thoughts

In this post, we see how easy it is leave unused files and dependencies in the code base. Fortunately, we can apply unimported command in pre-commit hook to lint files and dependencies before code commit. When Nestjs project has unused files and dependences, code commit fails until we resolve unimported errors. This way, we can keep clean code and avoid introducing technical debts that the successors have to deal with.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in NestJS and other technologies.

Resources:

  1. unimported: https://www.npmjs.com/package/unimported
  2. husky: https://www.npmjs.com/package/husky