How to create a node js command line tool with yargs middleware
Tags: node, cli, javascript
Published: 2018-08-19 18:09:35

---
title: How to create a node js command line tool with yargs middleware
published: true
tags: 
    - node
    - cli
    - javascript
canonical_url: https://khaledgarbaya.net/articles/how-to-create-a-node-js-command-line-tool-with-yargs-middleware
---

![macro-1452986 1920](https://images.ctfassets.net/3bc97k4uk5q7/1Oden9drYwQ2SkImweSUec/772d43a58924514a1fa1164bbcb57ce3/macro-1452986_1920.jpg)

Using [Express.js](https://expressjs.com/) a lot, I was always a big fan of the middleware approach when handling routes.

When I started building CLI tools I noticed that there is a lot of similarity between a server-side program and a command line tool. 

Think of the command that a user types as the route or URL. For example  `cli-tool project new` in a server environment will be the following url `example.com/project/new`.

A `Request` object in the cli world can be the `stdin` and the `Response` as the `stdout`.

A while ago I introduced the middleware concept to [yargs,](https://github.com/yargs/yargs) the main framework I was using to build clis.

You can check the [pull request](https://github.com/yargs/yargs/pull/881) if you want to checkout the code.

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>What is a middleware?

A middleware is a function that has access to the incoming data in our case will be the `argv`. It is usually executed before a yargs command.

Middleware functions can perform the following tasks:

- Execute any code.
- Make changes to the `argv`.
- End the request-response cycle.

```
                        -------------- -------------- ---------
stdin ----> argv ----> | Middleware 1 | ----> | Middleware 2 | ---> | Command |
                        -------------- -------------- ---------
```

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>What is yargs?

![yargs-logo](https://images.ctfassets.net/3bc97k4uk5q7/1Hfm8NHHUAMucykccM6e6s/94661dff604c33cdf1437cc27828f8a6/yargs-logo.png)

> Yargs helps you build interactive command line tools, by parsing arguments and generating an elegant user interface.

It's an amazing library that remove all the pain of parsing the command line args also it provides more features like:

- commands and (grouped) options.
- A dynamically generated help menu based on your arguments.
- bash-completion shortcuts for commands and options.

and [more](https://github.com/yargs/yargs/blob/master/docs/api.md)...

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>A simple Node.js command line tool with yargs

![bash-161382 1280](https://images.ctfassets.net/3bc97k4uk5q7/2RTr7jXAiQIUemKiagQeoW/6ebc3034dcd6cb5881cf333bdc4aa053/bash-161382_1280.png)

Let's create a simple command line program that authenticate the user saves the state to a file called `.credentials` to be used in the next commands.

```
const argv = require('yargs')
const fs = require ('fs')

argv
  .usage('Usage: $0 <command> [options]')
  .command('login', 'Authenticate user', (yargs) => {
        // login command options
        return yargs.option('username')
                    .option('password')
      },
      ({username, password}) => {
        // super secure login, don't try this at home
        if (username === 'admin' && password === 'password') {
          console.log('Successfully loggedin')
          fs.writeFileSync('~/.credentials', JSON.stringify({isLoggedIn: true, token:'very-very-very-secret'}))
        } else {
          console.log('Please provide a valid username and password')
        }
      }
   )
  .command('secret', 'Authenticate user', (yargs) => {
    return yargs.option('token')
  },
    ({token}) => {
      if( !token ) {
          const data = JSON.parse(fs.readFile('~/.credentials'))
          token = data.token
      }
      if (token === 'very-very-very-secret') {
        console.log('the secret word is `Eierschalensollbruchstellenverursacher`') // <-- that's a real german word btw.
      }
    }
  )
  .command('change-secret', 'Authenticate user', (yargs) => {
    return yargs.option('token')
  },
    ({token, secret}) => {
      if( !token ) {
          const data = JSON.parse(fs.readFile('~/.credentials'))
          token = data.token
      }
      if (token === 'very-very-very-secret') {
        console.log(`the new secret word is ${secret}`)
      }
    }
  )
  .argv;
```

The very first problem in the code is that you have a lot of duplicate code whenever you want to check if the user authenticated.

One more problem can popup is when more then one person is working on this. Adding another "secret" command feature will require someone to care about authentication, which is not ideal. What about an authentication function that gets called before every command and attach the token to your args.

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Adding yargs middleware

![building-674828 1280](https://images.ctfassets.net/3bc97k4uk5q7/6gOsorEKoowUwKoeeOYQQO/669f8d78ed8f9f113ca02c6c7d1123c7/building-674828_1280.jpg)

```
const argv = require('yargs')
const fs = require ('fs')
cosnt normalizeCredentials = (argv) => {
  if( !argv.token ) {
          const data = JSON.parse(fs.readFile('~/.credentials'))
          token = data.token
      }
  return {token} // this will be added to the args
}
const isAuthenticated = (argv) => {
  if (token !== 'very-very-very-secret') {
    throw new Error ('please login using the command mytool login command')
  }
  return {}
}
argv
  .usage('Usage: $0 <command> [options]')
  .command('login', 'Authenticate user', (yargs) => {
        // login command options
        return yargs.option('username')
                    .option('password')
      },
      ({username, password}) => {
        // super secure login, don't try this at home
        if (username === 'admin' && password === 'password') {
          console.log('Successfully loggedin')
          fs.writeFileSync('~/.credentials', JSON.stringify({isLoggedIn: true, token:'very-very-very-secret'}))
        } else {
          console.log('Please provide a valid username and password')
        }
      }
   )
  .command('secret', 'Authenticate user', (yargs) => {
    return yargs.option('token')
  },
    (argv) => {  
        console.log('the secret word is `Eierschalensollbruchstellenverursacher`') // <-- that's a real german word btw.
    }
  )
  .command('change-secret', 'Authenticate user', (yargs) => {
    return yargs.option('token')
  },
    (argv) => {
        console.log(`the new secret word is ${secret}`)
    }
  )
  .middleware(normalizeCredentials, isAuthenticated)
  .argv;
```

With these two small changes we now have cleaner commands code. This willl help you a lot when maintaining the code especially when you change the authentication code for example.Commands can be global, thanks to [aorinevo](https://github.com/yargs/yargs/pull/1119) or can be specific to a command which was the part I worked on.

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Can I use yargs middleware now?

To be able to use yargs you need to have the `@next` version installed.You can install it using `npm i yargs@next`.