DEV Community

Cover image for Stop Writing Ugly Terminal Output in Node.js
Yash Datir
Yash Datir

Posted on

Stop Writing Ugly Terminal Output in Node.js

A zero-dependency Node.js utility for colored logs, spinners, progress bars, tables, tree views, diffs, and OS notifications — all without looking up a single ANSI code.

You know the drill. You're deep into a Node.js CLI tool or a long-running script. The terminal output looks like this:

processing...
done
error
done
Enter fullscreen mode Exit fullscreen mode

Completely useless. No color. No structure. No idea what happened.

So you reach for chalk, then ora, then cli-table3, then diff, and suddenly your package.json has five new dependencies for the sole purpose of making console.log bearable.

budgie-console is a single package that handles all of it — with zero external dependencies.

npm install budgie-console
Enter fullscreen mode Exit fullscreen mode
const Console = require('budgie-console');
Enter fullscreen mode Exit fullscreen mode

That's the whole setup. Let's look at what you get.


The basics: colored log levels

Four methods that do what you actually want from a log level:

Console.success('Server started on port 3000');   // ✔  green
Console.error('Database connection refused');      // ✖  red
Console.warn('NODE_ENV is not set');               // ⚠  yellow
Console.info('Running Node ' + process.version);   // ℹ  cyan
Enter fullscreen mode Exit fullscreen mode

And the core log() method if you want full control over colors yourself:

Console.log(Console.Bright + Console.FgCyan, 'Bold cyan');
Console.log(Console.Underscore + Console.BgYellow, 'Underlined on yellow');
Console.log(Console.FgRed, 'Custom red message');
Enter fullscreen mode Exit fullscreen mode

All ANSI color and style constants are exposed directly — FgRed, BgGreen, Bright, Dim, Underscore, and the rest — so you can compose them freely without memorising escape sequences. Colors automatically disable themselves when NO_COLOR is set or the terminal is dumb.


Spinner — with a completion message

The spinner animates in-place using \r and stops cleanly when your statusFn returns false. The new doneMessage parameter means you don't need to awkwardly call Console.success() yourself afterwards and risk a race condition:

let running = true;

Console.spinner(
  ['', '', '', '', '', '', '', '', '', ''],
  'Fetching remote config...',
  80,
  () => running,
  'Config loaded!'       // ← printed atomically when spinner stops
);

fetchConfig().then(() => { running = false; });
Enter fullscreen mode Exit fullscreen mode
✔  Config loaded!
Enter fullscreen mode Exit fullscreen mode

It also returns a cancel function if you need to stop it imperatively:

const stop = Console.spinner(frames, 'Building...', 100, () => true);
// later:
stop();
Enter fullscreen mode Exit fullscreen mode

Progress bar — with cleanup

Call progress() from any loop or interval. When current >= total, it prints a newline so subsequent output starts on a fresh line (not tacked onto the end of the bar line — a subtle bug that's been fixed). Optional doneMessage here too:

let i = 0;
const iv = setInterval(() => {
  Console.progress(i, 50, 30, Console.FgCyan, 'Upload complete');
  if (++i > 50) clearInterval(iv);
}, 40);
Enter fullscreen mode Exit fullscreen mode
[████████████████████████████░░] 93%
✔  Upload complete
Enter fullscreen mode Exit fullscreen mode

Table — handles any cell type

Render bordered tables from a 2D array. Pass an optional header row as the second argument. Cell values don't need to be strings — numbers, booleans, null, and undefined are all coerced safely, so you can pass data directly from your objects without .toString()-ing everything:

Console.table(
  [
    ['Alice',   28,   true,  'Engineer'],
    ['Bob',     34,   false, 'Designer'],
    ['Charlie', null, true,  'Intern'  ],
  ],
  ['Name', 'Age', 'Active', 'Role']
);
Enter fullscreen mode Exit fullscreen mode
┌─────────┬──────┬────────┬──────────┐
│ Name    │ Age  │ Active │ Role     │
├─────────┼──────┼────────┼──────────┤
│ Alice   │ 28   │ true   │ Engineer │
│ Bob     │ 34   │ false  │ Designer │
│ Charlie │ null │ true   │ Intern   │
└─────────┴──────┴────────┴──────────┘
Enter fullscreen mode Exit fullscreen mode

Tree — pretty-print nested objects

Ever wanted something like util.inspect but actually readable in a terminal? Console.tree() renders any JS object or array as an indented tree with proper branch characters, like the Unix tree command. Primitives are color-coded: strings in green, numbers in cyan, booleans and null in yellow.

Console.tree({
  server: {
    host: 'localhost',
    port: 3000,
    ssl: false,
  },
  db: {
    name: 'myapp_prod',
    pool: 5,
    replicas: ['db1', 'db2'],
  },
}, 'config');
Enter fullscreen mode Exit fullscreen mode
└── config
    ├── server
    │   ├── host: "localhost"
    │   ├── port: 3000
    │   └── ssl: false
    └── db
        ├── name: "myapp_prod"
        ├── pool: 5
        └── replicas
            ├── 0: "db1"
            └── 1: "db2"
Enter fullscreen mode Exit fullscreen mode

Incredibly useful when debugging config objects, parsed JSON responses, or AST nodes. The second argument is the label for the root node — defaults to 'root'.


Diff — colored line-by-line comparison

No external dependency. Pure string comparison, line by line. Removed lines in red with -, added lines in green with +, unchanged lines dimmed. Perfect for showing what changed in a config file, an API response, or a generated file before writing it to disk:

const before = `
  host: localhost
  port: 3000
  debug: true
`.trim();

const after = `
  host: localhost
  port: 8080
  debug: true
  logLevel: info
`.trim();

Console.diff(before, after);
Enter fullscreen mode Exit fullscreen mode
  host: localhost
- port: 3000
+ port: 8080
  debug: true
+ logLevel: info
Enter fullscreen mode Exit fullscreen mode

No LCS algorithm, no diffing library, no download. Works fine for config summaries, migration previews, and anything where you're comparing two versions of a string.


Notify — OS desktop notifications

When a long build finishes, you don't want to keep staring at the terminal. Console.notify() fires a native OS notification so you can switch tabs and come back when it's done:

Console.notify('Build complete', 'Your production bundle is ready.');
Enter fullscreen mode Exit fullscreen mode

It shells out to osascript on macOS, notify-send on Linux, and PowerShell's NotifyIcon on Windows. No npm packages. If the underlying command isn't found, it warns to the terminal instead of throwing, so it degrades gracefully in CI or SSH sessions.

Linux note: requires libnotifysudo apt install libnotify-bin on Debian/Ubuntu.


The utilities you already know

A few more methods that round out the toolkit:

Box — wraps text in a bordered box, good for section headers in verbose output:

Console.box('Deployment complete', Console.FgGreen);
Enter fullscreen mode Exit fullscreen mode
┌──────────────────────┐
│  Deployment complete  │
└──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Divider — horizontal rule to visually separate output sections:

Console.divider();                           // ──────────── (40 chars, dim)
Console.divider('', 44, Console.FgCyan);
Enter fullscreen mode Exit fullscreen mode

Prompt — async readline input, returns a Promise:

const name = await Console.prompt('Your name:');
Console.success(`Hello, ${name}`);
Enter fullscreen mode Exit fullscreen mode

Clear — clears the terminal:

Console.clear();
Enter fullscreen mode Exit fullscreen mode

Install and use

npm install budgie-console
Enter fullscreen mode Exit fullscreen mode
const Console = require('budgie-console');

Console.success('Ready');
Console.table([['key', 'value']], ['Param', 'Result']);
Console.tree({ build: { env: 'production', minify: true } });
Console.notify('Done', 'Script finished');
Enter fullscreen mode Exit fullscreen mode

Zero config. Zero dependencies. Requires Node.js >=14. ANSI codes work out of the box on macOS and Linux; on Windows they work in Windows Terminal and VS Code's integrated terminal.

Source is on GitHub — yashdatir/budgie-console. Issues, PRs, and feedback welcome.


What do you reach for when you need terminal output to not look terrible? Drop it in the comments — always curious what other people are using.

Top comments (0)