Build a Simple Command Line Application with Node.js

Building command line applications is very fun and useful. You can learn a lot by making your own application. But make sure you pick the right language for the right job. If you need to do a lot of things in sequence, you may want to consider something else than Node, unless dealing with promises/streams/callbacks are a delight for you.

In this post we are going to be making our own simple http server with a connect middleware. There are many posts out there about making a server in Node, but it's really hard to find one that is straightforward and up-to-date. By the end of this post, you will be able to do something like this:

Project Files

The project is hosted on github. Check it out:

Installing the application

You just need to cd to the project folder and do npm install -g. That's all. Then if you do which simpleServer, it will tell you where it is symlinked. Then do simpleServer without any arguments and you should see the help!

Let's Get Started

Make a folder on your desktop and call it simpleServer and cd to it:

cd ~/Desktop && mkdir simpleServer && cd $_

Run npm init and just keep on hitting enter to accept the defaults:

npm init

Now you should have a package.json for yourself that looks like this:

{
  "name": "simpleServer",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Now lets install all the node modules that we will need:

npm install chalk chip colors path commander connect http serve-index serve-static --save

After running this, your node_modules folder will look like this:

node_modules/
├── chalk: pretty printing to the console.
├── chip: pretty printing to the console.
├── colors: pretty print colored messages to the console.
├── commander: parses command line arguments
├── connect: the connect middleware handling the middle man work
├── http: duh!
├── path: used for parsing directory path
├── serve-index: connect module for showing the index of the files
└── serve-static: connect module for serving static files

Now we are ready to write the main entry point to the program. Make a file and call it simpleServer.js:

touch simpleServer.js

Open the file and add this very important line as the first line:

#!/usr/bin/env node

Now open the package.json file and declare the simpleServer.js file as the entry file and specify the name of the application in the bin entry:

"bin": {
  "simpleServer": "simpleServer.js"
}

So your package.json looks like this now:

{
  "name": "simpleServer",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chalk": "^0.5.1",
    "chip": "0.0.5",
    "colors": "^1.0.3",
    "commander": "^2.5.0",
    "connect": "^3.3.3",
    "http": "0.0.0",
    "path": "^0.4.9",
    "serve-index": "^1.5.0",
    "serve-static": "^1.7.1"
  },
  "bin": {
    "simpleServer": "simpleServer.js"
  }
}

If you want change the main entry to 'simpleServer.jsinstead ofindex.js`. No big deal.

Now create a file and call it myserver.js and paste the following there:

var connect = require("connect"); // middleware
var serveStatic = require("serve-static"); // connect module for serving static files.
var serverIndex = require("serve-index"); // connect mofule for indexing
var pathUtil = require('path') // helper for resolving path.
var http = require('http'); // http server
var help = require('./help'); // helper for logging.

var myserver = {
  listen: function (port, publicPath, fn) {
    port = port || 8228; // default 8228
    publicPath = pathUtil.resolve(publicPath || '.') // serve current directory by default.
    // instantiate middleware
    var app = connect()
      .use(serverIndex(publicPath))
      .use(serveStatic(publicPath));
    // Instantiate server
    var server = http.createServer(app).listen(port, fn);
    help.log("Server running at port " + port + " ...");
  }
};
module.exports = myserver;

The script is pretty straightforward. It loads bunch of modules and then consumes them in the script. The code is commented out but the part that is very important is the line where we are setting up the middleware with connect:

var app = connect().use(serverIndex(publicPath))
         .use(serveStatic(publicPath));

The connect middleware used to have indexing and static serving built-in. But in the new version, those are split up into their own modules and that's why we are requiring them in the beginning of the script. Essentially, the app variable holds a reference to the middleware that we are gonna pass ot the http server to consume.

var server = http.createServer(app).listen(port, fn);

and at the last line we are adding the module to the exports object so that we can use it in the entry script later:

module.exports = myserver;

We are assigning myserver to the module.exports object and that is how we are going to access it in the main entry script.

Writing the help module is pretty straighforward. We are just going to write a short description of what the application is and what the options are. So make a file called help.js and paste the following:

var chalk = require('chalk'); // used for pretty printing to the console.
var log = require('chip')(); // helper for logging.
var colors = require('colors'); // add colors to the console.

var help = {
  print: function () {
    console.log('  Keywords:\n');
    console.log('    serve: spins up a server \n');
    console.log('  Description:\n');
    console.log('    A simple http server with the connect middleware for serving static websites. \n');
    console.log('  Examples:\n'.inverse.green);
    console.log('    Running a server at port 9000');
    console.log('        simpleServer serve -p 9000\n'.white);
    console.log('    Running a server at port 9000 serving the site folder');
    console.log('        simpleServer serve -p 9000 -d site\n'.white);
  },
  // helpers for printing to the console.
  log:   function (msg) { log.info(chalk.green(msg))},
  info:  function (msg) { log.debug(chalk.magenta(msg))},
  warn:  function (msg) { log.warn(chalk.yellow(msg))},
  error: function (msg) { log.error(chalk.red(msg))}
};
    module.exports = help;

We are basically writing the manifest of the application. And the obvioius part is that we are loading some other modules to help us pretty print to the console. And at the last line we are adding this module to the global exports object so that we can access it in other places. As we did in myserver.js.

And last but not least is the main entry to the program, the simpleServer.js. At this point there is only one line in there:

#!/usr/bin/env node

This line basically tells the system to use node as the interpreter. Note that we are using the /usr/bin/env node for the shebang not /usr/local/bin/node. It has to do with the way bash is involved in interpreting the command. Look it up, I am sure there is a question about it on Stackoverflow ;)

var server = require("./myserver");
var help = require('./help'); // helper for logging.
var program = require('commander'); // deals with reading command line arguments.
program
  .version('0.1.0')
  .usage('<keywords> [options]')
  .option('-p, --port [Port number]', 'Port number running the server')
  .option('-d, --directory [directory serving]', 'The path being served')
  .on('--help', function(){ help.print() })
  .parse(process.argv);

//helper
function anyArgs() { return !!program.args.length; }
if(!anyArgs()) {
  program.help();
} else {
  if (program.args[0] === "serve") {
    server.listen( program.port, program.directory )
  } else {
    program.help();
  }
}

All we are doing here is loading myserver, help and the commander modules. We use the commander module to get the command line arguments and use that to run the correct command:

if (program.args[0] === "serve") {
  server.listen( program.port, program.directory )
 }

This line is basically saying if the first argument is serve then run the server. In this context the first argument is:

simpleServer firstArg

Followed by switches:

simpleServer firstArg -p port# -d PathValue

Now let's just quickly run the script:

node simpleServer.js

If you run this with no argument, you should get the the help screen. Now let's pass it a port number:

node simpleServer.js serve -p 8987

You should see this as the output:

✔ Server running at port 8987 ...

Now, if you navigate to http://localhost:8987 you should see that the server is running serving the folder you are running the application from.

Now lets run the server at the same port but serve another folder:

node simpleServer.js serve -p 8987 -d site

Now, we want to install it globally so that we can run the command anywhere. Run the following command to install the tool:

npm install -g

That's it! Now if you do which simpleServer you should get an output telling you where the module is. Try it out! Make a folder, cd to it and just run:

simpleServer serve -p 8998 -d .

This will start the server at port 8998 and serves the current directory. Open your browser and go to http://localhost:8998 and you should see the folder being served. Done !