How To Build a CLI With Node.js and React
Tags: javascript, node, react, typescript
Published: 2022-11-05 17:27:24

---
title: How To Build a CLI With Node.js and React
published: true
canonical_url: https://pmbanugo.me/how-to-build-a-cli-with-nodejs-and-react
description: In this tutorial, you will learn how to build command-line applications using React and Node.js.
tags: javascript, node, react, typescript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6nvv1j3db1atrhps1wz.png
# Use a ratio of 100:42 for best results.
---

A command-line interface (CLI) is a computer program that processes commands in the form of text inputs and in turn executes system functions. In the early days of computing, the only way to interact with a computer was through the terminal. But today, many users interact with the computer via a graphical user interface (GUI). As a software developer, you are likely to have used a CLI. If you're a Node.js or React developer, you use the npm CLI (almost) every day.

You probably have used a CLI to install or configure software or automate repetitive tasks. There are many use cases for building a CLI and knowing how to do that with your JavaScript and React.js skills can be productive.

## Understanding the Turorial's Project

This tutorial will show you how to build a CLI using Node.js and React. The CLI will be used to create a Node.js microservice project. It will be similar to how the create-react-app works in that you can run it using npx or just install it as a global package.

## Prerequisite

To follow along with this tutorial, you should know how to use React and Node.js. You also need to have Node.js and npm installed.

## Set Up the CLI Project

You're going to build the CLI using [Ink](https://github.com/vadimdemedes/ink), a React component-based library for building interactive CLIs. It uses [Yoga](https://github.com/facebook/yoga) to build Flexbox layouts in the terminal, so most CSS-like props are available in Ink as well. Ink is simply a React renderer for the terminal, so all the React features are supported. No need to learn a new syntax specific to Ink.

Run the commands below to create the project.

```bash
mkdir create-micro-service && cd create-micro-service
npx create-ink-app
```

After the command is processed, you should have the project files and dependencies installed, and a symlink created for the CLI. Open your terminal and run the command `create-micro-service`. You should get the message _"Hello, Stranger"_ printed out. If you run `create-micro-service --help`, you should get the following help message.

```
  Usage
    $ create-micro-service

  Options
        --name  Your name

  Examples
    $ create-micro-service --name=Jane
    Hello, Jane
```

### Processing Arguments and Flags

There are two important files in the project directory, namely **cli.js** and **ui.js**. The cli.js file is the entry point into the application, and ui.js is the React component that renders the message _Hello, Stranger_. In cli.js you will find the following code:

```javascript
#!/usr/bin/env node
"use strict";
const React = require("react");
const importJsx = require("import-jsx");
const { render } = require("ink");
const meow = require("meow");

const ui = importJsx("./ui");

const cli = meow(`
	Usage
	  $ create-micro-service

	Options
		--name  Your name

	Examples
	  $ create-micro-service --name=Jane
	  Hello, Jane
`);

render(React.createElement(ui, cli.flags));
```

You will notice that it is using meow to read the command and flags. Afterward, it passes the flags to the **ui** component and renders it. [meow](https://www.npmjs.com/package/meow) is a helper library to parse arguments. The text passed to `meow()` is the help text shown when you call `create-micro-service --help`.

## Modify the Help Text

The CLI will accept a `--name` flag to use as the directory name for the project to scaffold. Let's modify the help text to indicate that. Open cli.js and update the statements from lines 10 to 20 with the code below.

```js
const cli = meow(`
	Usage
	  $ create-micro-service

	Options
		--name  Your project's name.

	Examples
	  $ create-micro-service --name=order-service

`);
```

That's all that is needed for now. The next step is for it to create the directory with the necessary files and dependencies.

## Copy the Project Files and Install the Dependencies

When the CLI executes, it should create a new directory and clone a Node.js project template to it. Afterward, it will install some dependencies needed in the project. Let's start with cloning the project files. There are different ways you can do this, for example, starting a child process and executing a git command to clone the project. For this tutorial, you're going to use [degit](https://www.npmjs.com/package/degit). degit will make a local copy of a git repository without copying the git history.

Install degit by running the command `npm i degit`.

Create a new file named **init.js** and paste the code below into it.

```js
const degit = require("degit");

exports.clone = async (name) => {
  const emitter = degit("github:kazi-faas/function-template-js");
  await emitter.clone(`./${name}`);
};
```

The code above initializes the degit module with the name of the git repository to clone. It is **function-template-js** repository under the **kazi-faas** organization. The string value is prefixed with `github:` so that it knows the Git server to use. If you use GitLab, it will be `gitlab:`.

The _clone()_ function will then clone the files into the specified directory. The directory name will be the `--name` flag from the command line. You can find the template’s GitHub repository at [github.com/kazi-faas/function-template-js](https://github.com/kazi-faas/function-template-js).

After the template is cloned, a package.json file will be added. You can simply use the _fs_ module to create and write to this file. For this tutorial, you're going to use the **write-pkg** npm package for this. Its single purpose is creating package.json files.

Open your terminal and run the command `npm i write-pkg@4` to install the package.

Then add the following require statements in init.js.

```js
const { join } = require("path");
const writePackage = require("write-pkg");
```

Copy and paste the `addPackageJson()` function below to the same file.

```js
exports.addPackageJson = async (name) => {
  const pkg = {
    name,
    description: "A function which responds to HTTP requests",
    main: "index.js",
    scripts: {
      start: "micro",
      dev: "micro-dev",
    },
  };

  await writePackage(join(process.cwd(), name, "package.json"), pkg);
};
```

The `addPackageJson()` will create a package.json file for the project. It will use the `name` parameter as the package name, and it includes two scripts, namely _start_ and _dev_.

After adding the package.json file, the CLI should install the dependencies required to run the app. The dependencies it will install are:

1. [micro](https://www.npmjs.com/package/micro): A library for asynchronous HTTP microservices.
2. micro-dev: It's the same as micro but specifically for development purposes.

You're going to use [pkg-install](https://www.npmjs.com/package/pkg-install) to install npm packages. pkg-install makes it easy to install npm packages irrespective of the package manager. Open the terminal and run `npm i pkg-install` to install it.

After it's installed, open init.js and import the library.

```js
const { install } = require("pkg-install");
```

Then copy and paste the function below to init.js as well.

```js
exports.installDependencies = async (name) => {
  const dir = join(process.cwd(), name);
  await install({ micro: "^9.3.4" }, { cwd: dir });
  await install({ "micro-dev": "^3.0.0" }, { cwd: dir, dev: true });
};
```

The `install` function is called with the package name and version, and the directory to install. You used two different calls to `install()` because we want to install `micro-dev` as a devDependency (see the `dev: true` option).

## Update the React Component

You're going to update ui.js to scaffold a new project and show the progress of that action in the terminal. You will use [ink-task-list](https://github.com/privatenumber/ink-task-list), a task list component for Ink, to display a list of tasks and their progress. You need to install this component to use it.

Open your terminal and run the command `npm i ink-task-list` to install it. After that, open ui.js and the following require statements

```js
const { Task, TaskList } = require("ink-task-list");
const { addPackageJson, clone, installDependencies } = require("./init");
```

Change the default value for the `name` prop from `Stranger` to `micro-service`. The CLI should use _micro-service_ as the directory/project name if none is supplied by the user.

Update the `App` functional component with the code below.

```js
const App = ({ name = "micro-service" }) => {
  const [tasks, setTasks] = React.useState([
    { state: "loading", label: "Downloading files." },
  ]);

  React.useEffect(() => {
    async function scaffold() {
      await clone(name);
      setTasks([
        { state: "success", label: "Downloaded files." },
        { state: "loading", label: "Adding package.json." },
      ]);

      await addPackageJson(name);
      setTasks((prevState) => [
        { ...prevState[0] },
        { state: "success", label: "Added package.json." },
        { state: "loading", label: "Installing dependencies." },
      ]);

      await installDependencies(name);
      setTasks((prevState) => [
        { ...prevState[0] },
        { ...prevState[1] },
        { state: "success", label: "Installed dependencies." },
      ]);
    }

    scaffold();
  }, []);

  return (
    <TaskList>
      {tasks.map(({ label, state }, index) => (
        <Task key={index} label={label} state={state} />
      ))}
    </TaskList>
  );
};
```

The component calls the `clone()`, `addPackageJson()` and `addPackageJson()` functions respectively, and updates the state after each function call. The `tasks` variable is an array of objects with `state` and `label` properties. These properties are used to render the label and an icon that indicates if a task is in progress or completed.

## Your CLI in Action

You now have all the code necessary for this tutorial's project. Let's try it out and see that it works. Open your terminal and run the command `create-micro-service --name=demo`. This will scaffold a microservice project which you will find in the **demo** directory.

![create-micro-service](create-micro-service.mov "video output when create-micro-service command runs")

## What Next?

Now that you have built a CLI to scaffold microservice projects, feel free to go wild with your ideas and build CLIs to automate your workflow. You can start with extending the project you built while following this tutorial. You can extend it to include a flag to choose if the dependencies should be installed using npm or yarn. The pkg-install module has an option to set the preferred package manager.

There are other command-line libraries to help you out. You can find some of them listed in the [awesome-nodejs](https://github.com/sindresorhus/awesome-nodejs#command-line-utilities) GitHub repository. I will also recommend that you read the guidelines at [clig.dev](clig.dev). It's an open-source guide to help you write better command-line programs.

You can get this tutorial's code on GitHub—[github.com/pmbanugo/create-micro-service-tutorial](https://github.com/pmbanugo/create-micro-service-tutorial)

Feel free to reach out to me if you have questions. I'm happy to help or point you in the right direction.

> Originally published on Telerik blogs: https://www.telerik.com/blogs/how-to-build-cli-node-react