How to Make a CLI App with NestJS: Step-by-Step

How to Make a CLI App with NestJS: Step-by-Step

·

4 min read

I recently started a role on a company's developer experience team. One of the responsibilities that comes along with this is building larger CLI applications.

As I wrote previously, I use the commander library when writing DevOps scripts for my NodeJS projects. This is great for smaller utilities that only a couple of people will use, but what about if it needs to interact with multiple services and have lots of nested commands?

As it turns out NestJS supports this using the same commander library that I used previously. In this article, I'll show you how to create a basic CLI using this setup.

Creating the Project

Creating the CLI project is the same as creating an API with NestJS. Simply follow the basic workflow on nestjs website.

npm i -g @nestjs/cli
nest new my-cli-project
cd my-cli-project

Setup nest-commander

The nest-commander library provides all the code that you'll need to create your commander CLI in the background. Like every other node dependency, the first step is to install the package.

npm i nest-commander

Creating a Command

Just like with normal nestjs development, we need to create a module that our command will live in.

Nest-Commander provides some tools that plug into the nest cli. I've tried using them but wasn't able to get them to work. You may have better luck, but I'll continue with the manual process.

nest g module CowSay

Normally you would create a controller or resolver in your module using the nest cli. Since we're creating a cli, we want to create a Command instead. Create a cow-say.command.ts file in the src/cow-say folder and open it.

Each command that you create will extend the CommandRunner class and use the @Command() decorator. In the cow-say.command.ts file that you just created, add the following.

import { Command, CommandRunner } from 'nest-commander';

@Command({
  name: 'cowsay',
  options: {
    isDefault: true,
  },
})
export class CowSayCommand extends CommandRunner {
  async run(): Promise<void> {
  }
}

To get this command to display something, import the cowsay library.

import * as cowsay from 'cowsay';

You'll need to install it too...

npm i cowsay

...and update the run method in cow-say.command.ts

async run(): Promise<void> {
  console.log(cowsay.say({ text: 'Hello World!' }));
}

Wiring Everything Together

Now that the command is created, you need to register it as part of the module. Open the cow-say.module.ts file and add CowSayCommand as a provider.

import { Module } from '@nestjs/common';
import { CowSayCommand } from './cow-say.command';

@Module({
  providers: [CowSayCommand],
})
export class CowSayModule {}

Tell NestJS that it's not a server

Now comes the tricky part. The nest project that you created is setup to create a REST API by default, but you don't need that. So delete the app service and controller.

rm app.service.ts
rm app.controller.ts

Next, update the AppModule to import the CowSay module.

import { Module } from '@nestjs/common';
import { CowSayModule } from './cow-say/cow-say.module';

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

Finally, you need to update the main.ts file. You'll change the bootstrap function to use CommandFactory instead of NestFactory.

import { CommandFactory } from 'nest-commander';
import { AppModule } from './app.module';

async function bootstrap() {
  await CommandFactory.run(AppModule);
}
bootstrap();

Running It

I like to have the option of running my CLI tools without having to run a TypeScript build step. This helps speed up development.

To run your CLI without building it, you'll be using the ts-node package. To get started install it as a development dependency.

npm i -D ts-node

Now add a new script to package.json

"start:cli": "ts-node src/main.ts"

...and you can test your CLI by running the script

❯ npm run start:cli

> my-cli-project@0.0.1 start:cli
> ts-node src/main.ts

 ______________
< Hello World! >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Summary

This article showed how to get started building your very first NestJS CLI application. Taking this approach can help you build a larger application that is maintainable. In future articles I'll be introducing you to more advanced features of this setup.

Did you find this article valuable?

Support Ben by becoming a sponsor. Any amount is appreciated!