Chapter 35: Node.js Util Module
1. What is the util module?
The util module is a small but extremely useful collection of utility functions that don’t belong anywhere else.
It’s like Node.js’s “miscellaneous helpers” drawer.
Most functions in util are not glamorous, but they save you from writing the same 5–15 lines of boilerplate code over and over again.
In 2025–2026 reality:
- Some functions are very frequently used (especially promisify, inspect, format)
- Some are almost never used anymore (legacy helpers)
- The module is very stable — almost no breaking changes for years
2. Modern import style (recommended)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Best style 2025–2026 import util from 'node:util'; // or named imports (very clean & common) import { promisify, inspect, format, callbackify, isDeepStrictEqual, TextDecoder, TextEncoder } from 'node:util'; |
Always use node:util prefix — makes it obvious it’s core Node.
3. The most important & frequently used functions
Let’s go through the ones you will actually use in real projects.
3.1 util.promisify(original) — the #1 most useful function
Takes a callback-style function and turns it into a promise-style function.
Classic example – before & after
Before (callback hell)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const fs = require('node:fs'); fs.readFile('config.json', 'utf8', (err, data) => { if (err) { console.error(err); return; } try { const config = JSON.parse(data); console.log(config); } catch (e) { console.error('Invalid JSON', e); } }); |
After — using promisify
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import fs from 'node:fs'; import { promisify } from 'node:util'; const readFile = promisify(fs.readFile); async function loadConfig() { try { const data = await readFile('config.json', 'utf8'); return JSON.parse(data); } catch (err) { console.error('Config load failed:', err.message); throw err; } } |
Very common real pattern – promisify entire modules
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { promisify } from 'node:util'; import dns from 'node:dns'; const dnsLookup = promisify(dns.lookup); async function resolveHost(host) { const { address } = await dnsLookup(host); return address; } |
Even better 2025–2026 style: Many core modules already have promise versions — prefer them when available:
|
0 1 2 3 4 5 6 7 8 9 10 |
// Prefer this import dns from 'node:dns/promises'; // Instead of const dnsLookup = promisify(require('node:dns').lookup); |
3.2 util.inspect(value, options?) — the best way to print objects
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import util from 'node:util'; const user = { id: 123, name: 'Aman', roles: ['admin', 'developer'], address: { city: 'Hyderabad', pin: 500081 }, active: true }; console.log(util.inspect(user, { depth: Infinity, // show everything colors: true, // nice colors in terminal compact: false, // more readable breakLength: 80 // line wrapping })); |
Very useful flags
| Option | Typical value | Effect |
|---|---|---|
| depth | null or 5 | How deep to go into nested objects (null = infinite) |
| colors | true | Color output in terminal |
| compact | false | More readable multi-line output |
| maxArrayLength | 100 | Limit long arrays |
| showHidden | true | Show non-enumerable properties |
Common pattern – debug helper
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function debug(...args) { console.log( util.inspect(args.length === 1 ? args[0] : args, { depth: null, colors: true, compact: false }) ); } |
3.3 util.format(formatString, …values) — better than string concatenation
|
0 1 2 3 4 5 6 7 8 9 10 11 |
console.log(util.format('User %s (id: %d) logged in from %s', 'Aman', 123, 'Hyderabad')); // → User Aman (id: 123) logged in from Hyderabad console.log(util.format('Error: %o', new Error('Something broke'))); // → Error: Error: Something broke // at ... (full stack trace) |
Supported placeholders
- %s → string
- %d → number
- %j → JSON
- %o → object (nice inspect)
- %% → literal %
Modern alternative: Many people just use template literals now:
|
0 1 2 3 4 5 6 |
console.log(`User ${name} (id: ${id}) logged in`); |
But util.format is still very useful when you want consistent formatting or when working with legacy code.
3.4 util.isDeepStrictEqual(val1, val2) — deep equality check
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { isDeepStrictEqual } from 'node:util'; const obj1 = { a: 1, b: { c: 2 } }; const obj2 = { a: 1, b: { c: 2 } }; const obj3 = { a: 1, b: { c: 3 } }; console.log(isDeepStrictEqual(obj1, obj2)); // true console.log(isDeepStrictEqual(obj1, obj3)); // false |
Used in:
- Testing
- Memoization / caching
- State comparison
Note: assert.deepStrictEqual uses this under the hood.
4. Other useful functions (less common but still important)
| Function | What it does | When you use it |
|---|---|---|
| callbackify(original) | Turn promise function → callback function | Making promise APIs work with old callback code |
| TextDecoder / TextEncoder | Convert between Buffer ↔ string (UTF-8, etc.) | Low-level encoding/decoding |
| types.isPromise(val) | Check if value is a Promise | Defensive coding |
| types.isDate(val) | Check if value is Date object | Type guards |
Example – callbackify
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { callbackify } from 'node:util'; const readFileAsync = promisify(fs.readFile); const readFileCallback = callbackify(readFileAsync); readFileCallback('config.json', 'utf8', (err, data) => { if (err) console.error(err); else console.log(data); }); |
5. Real-world patterns (how professionals use util)
Pattern 1 – Debug / logging helper (very common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import util from 'node:util'; function debug(...args) { const timestamp = new Date().toISOString(); console.log( `[DEBUG ${timestamp}]`, util.inspect(args, { depth: null, colors: true, compact: false }) ); } |
Pattern 2 – Safe deep comparison in caching / memoization
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const cache = new Map(); function memoize(fn) { return async (...args) => { const key = util.inspect(args, { depth: null }); if (cache.has(key)) { return cache.get(key); } const result = await fn(...args); cache.set(key, result); return result; }; } |
Pattern 3 – Formatting log messages
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
logger.info(util.format( 'User %s (id %d) performed %s from IP %s', user.name, user.id, action, ip )); |
6. Common mistakes & traps
| Mistake | Consequence | Fix |
|---|---|---|
| Using util.inspect without {depth: null} | Truncated output for deep objects | Always set depth: null or Infinity |
| Using util.format with huge objects | Very large log lines | Prefer inspect or template literals |
| Using callbackify on already promise-based functions | Double wrapping → bugs | Check if function already returns promise |
| Relying on util.is* for type checking | Not exhaustive | Prefer TypeScript types when possible |
Summary – Quick cheat sheet 2025–2026
| You want to… | Use this function | Typical real-world use case |
|---|---|---|
| Convert callback API → promise API | promisify(fn) | Bridge old libraries to async/await |
| Pretty-print objects / debug | util.inspect(value, {depth: null, colors: true}) | Logging, debugging, test output |
| Format strings safely | util.format(template, …values) | Structured logging |
| Deep equality check | util.isDeepStrictEqual(a, b) | Memoization, testing, state comparison |
| Turn promise function → callback | callbackify(asyncFn) | Compatibility with old callback code |
Would you like to go much deeper into any specific area?
- Full promisify all common core modules example
- Building a real debug logger with util.inspect
- Using callbackify in legacy migration scenarios
- util.inspectcustom inspection (symbols, classes)
- Comparison: util.inspect vs console.dir vs JSON.stringify
Just tell me which direction feels most useful — I’ll continue with detailed, production-ready examples. 😊
