How to Make a CLI App with NestJS: Step-by-Step
Tags: nestjs, cli, typescript
Published: 2023-09-16 11:52:35

---
title: How to Make a CLI App with NestJS: Step-by-Step
published: true
date: 2023-09-15 20:40:44 UTC
tags:
  - nestjs
  - CLI
  - TypeScript
canonical_url: https://thebenforce.com/post/how-to-make-a-cli-app-with-nestjs-step-by-step/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztqc2stdojp6ovcfg9xy.png
---

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](https://thebenforce.com/post/my-node-js-toolkit-for-devops-scripting), 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](https://docs.nestjs.com/cli/overview).

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

## Setup `nest-commander`

The [`nest-commander`](https://nest-commander.jaymcdoniel.dev/) 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.

```bash
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](https://nest-commander.jaymcdoniel.dev/en/schematics/usage/). 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.

```bash
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.

```typescript
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.

```typescript
import * as cowsay from 'cowsay';
```

You'll need to install it too...

```typescript
npm i cowsay
```

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

```typescript
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.

```typescript
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.

```bash
rm app.service.ts
rm app.controller.ts
```

Next, update the `AppModule` to import the `CowSay` module.

```typescript
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`.

```typescript
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.

```bash
npm i -D ts-node
```

Now add a new script to `package.json`

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

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

```bash
❯ 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.