Appearance
Pretty Error Formatting
When to use
Use pretty error formatting when catching errors in CLI tools or development scripts and the default Node.js stack trace is hard to read. Pretty error formatting turns dense stack traces into readable, color-highlighted output that helps developers find the problem faster.
The pattern
Two approaches depending on context:
pretty-errorpackage — Renders anyErrorobject as a formatted, colorized stack trace. Best for generic catch blocks in CLI tools.- Contextual error enhancement — Combine the error with domain-specific diagnostics (like lint output) to give the developer actionable information.
Using pretty-error
Wrap error output in catch blocks with PrettyError to replace the default single-line stack trace with a structured, color-coded display. This example configures pretty-error to skip Node internals and specific packages, giving a clean stack trace for CLI tool errors:
javascript
import PrettyError from "pretty-error";
const prettyError = new PrettyError();
// Skip Node internals in stack traces
prettyError.skipNodeFiles();
// Skip specific packages
prettyError.skipPackage("express");
program.action(async () => {
try {
await runMigrations();
} catch (error) {
console.error(prettyError.render(error));
process.exit(1);
}
});The rendered output highlights:
- Error message (bold)
- File paths and line numbers (color-coded)
- Node internals (dimmed or hidden)
- Indented call frames (easy to scan)
For commands that run interactively with inherited stdio, catch and render errors the same way:
javascript
async function interactive(command, options = {}) {
try {
return await execa.command(command, {
shell: true,
preferLocal: true,
stdio: "inherit",
...options,
});
} catch (error) {
console.log(prettyError.render(error));
}
}Contextual error enhancement
When a domain-specific tool can provide better diagnostics than the raw stack trace, combine both:
javascript
import ejsLint from "ejs-lint";
function renderTemplate(templateContent, data) {
try {
return ejs.render(templateContent, data);
} catch (error) {
console.error(error.message);
const lintResult = ejsLint(templateContent);
if (lintResult) {
console.error("Template lint errors:", lintResult);
}
throw error;
}
}This gives the developer two signals: what went wrong (the error) and where to look (the lint output).
Fail-fast with graceful degradation
The general principle: catch errors early and present them clearly. Don't swallow errors silently, and don't dump raw objects to the console:
javascript
async function deploy(config) {
try {
await buildProject(config);
await uploadArtifacts(config);
} catch (error) {
// Readable error first
console.error(prettyError.render(error));
// Exit with non-zero code
process.exit(1);
}
}Trade-offs
pretty-erroradds a dependency. It's small and stable, but for minimal CLI tools you might prefer a simpler approach like just printingerror.messageanderror.code.- Color output doesn't work everywhere. Redirected output, CI logs, and some terminals don't support ANSI colors. The
pretty-errorpackage handles this gracefully, but test in your target environment. - Contextual diagnostics require domain knowledge. The lint-on-error pattern only works when you have a tool that can analyze the input. For generic errors, stick with
pretty-error. - Hiding stack frames can obscure bugs. Skipping Node internals is helpful for application errors but can hide important context for lower-level issues. Use
skipNodeFiles()selectively.