Background image

Node.js Scripting Cheatsheet (Battle Tested)

File-System Essentials

Files CRUD
file.ts
import fs from 'node:fs/promises'

await fs.readFile('file.txt', 'utf8')
await fs.writeFile('file.txt', 'data')
await fs.appendFile('file.txt', 'more')
await fs.copyFile('src.txt', 'dest.txt')
await fs.rename('old.txt', 'new.txt')
await fs.rm('file.txt')
,

import fs from 'node:fs/promises'

await fs.readFile('file.txt', 'utf8')
await fs.writeFile('file.txt', 'data')
await fs.appendFile('file.txt', 'more')
await fs.copyFile('src.txt', 'dest.txt')
await fs.rename('old.txt', 'new.txt')
await fs.rm('file.txt')

Nodejs Docs

Directory walk
import {readdir} from 'node:fs/promises'
import {join} from 'node:path'

async function* walk(dir) {
    for (const entry of await readdir(dir, {withFileTypes: true})) {
        const res = join(dir, entry.name)
        if (entry.isDirectory()) yield* walk(res)
        else yield res
    }
}

for await (const file of walk('src')) console.log(file)
Large-file streams createReadStream
import {createReadStream} from 'node:fs'
import readline from 'node:readline'

const rl = readline.createInterface({input: createReadStream('large.log')})
for await (const line of rl) {
    // do something with each line
}
unzip archives
import fs from 'node:fs'
import unzipper from 'unzipper'

export function unzip(archivePath: string, dest = './out'): Promise<void> {
    return fs
        .createReadStream(archivePath)
        .pipe(unzipper.Extract({path: dest}))
        .promise() // unzipper adds a neat promise helper
}
ZIP with exclude
import fs from 'node:fs'
import archiver from 'archiver'

export async function zipDir(src: string, out = 'archive.zip'): Promise<void> {
    return new Promise((resolve, reject) => {
        const output = fs.createWriteStream(out)
        const archive = archiver('zip', {zlib: {level: 9}})

        output.on('close', resolve)
        archive.on('error', reject)
        archive.pipe(output)

        // include only .js/.ts/.json, skip maps, dot-files, node_modules
        archive.glob('**/*.{js,ts,json}', {
            cwd: src,
            ignore: ['node_modules/**', '**/*.map', '**/.*']
        })

        archive.finalize()
    })
}
Directories CRUD
import fs from 'node:fs/promises'

await fs.mkdir('dir', {recursive: true})
await fs.rmdir('dir')
await fs.rm('dir', {recursive: true})
await fs.readdir('dir') // return files
await fs.cp('src/', 'dest/', {recursive: true}) // copy
Glob
import fg from 'fast-glob' // external dependency

const files = await fg('**/*.js', {cwd: 'src'})
GZIP file
import {createReadStream, createWriteStream} from 'node:fs'
import {createGzip} from 'node:zlib'

createReadStream('data.json').pipe(createGzip()).pipe(createWriteStream('data.json.gz'))
import archiver from 'archiver'
import {createWriteStream} from 'node:fs'

const output = createWriteStream('site.zip')
const archive = archiver('zip', {zlib: {level: 9}})

archive.pipe(output)
archive.directory('public/', false)
await archive.finalize()
ZIP
import {zip} from 'zip-a-folder'

export async function zipDir(src: string, out = 'archive.zip') {
    await zip(src, out)
}

CLI Parameters & Environment

Raw process.argv parsing
// node index.js deploy
process.argv
// [0] node [1] index.js [2] deploy

const [, , ...args] = process.argv // or  process.argv.slice(2)
// args: [0] deploy
CLI yargs
import yargs from 'yargs'

const argv = yargs
    .command('deploy', 'Deploy the app', y => y.option('env', {type: 'string', default: 'dev'}))
    .help().argv

console.log(argv.env)
Interactive prompts (inquirer, prompts)
import prompts from 'prompts'

const response = await prompts({
    type: 'select',
    name: 'region',
    message: 'Pick a region',
    choices: [
        {title: 'US East', value: 'us-east1'},
        {title: 'Europe', value: 'europe-west1'}
    ]
})
console.log(response.region)
  • ENV access with process.env
const apiKey = process.env.API_KEY
CLI (no libs)
// @returns {{ [key: string]: string|boolean }}
function parseArgs(argv) {
    const result = {}
    let currentKey = null

    argv.forEach(token => {
        if (token.startsWith('--')) {
            // Long flag or key
            currentKey = token.slice(2)
            result[currentKey] = true
        } else if (token.startsWith('-')) {
            // Short flag or key
            currentKey = token.slice(1)
            result[currentKey] = true
        } else if (currentKey) {
            // Assign value to the last key
            result[currentKey] = token
            currentKey = null
        }
    })

    return result
}

parseArgs(process.argv.slice(2))
CLI commander
import {Command} from 'commander'

const program = new Command()

program
    .name('mycli')
    .description('Build or deploy your project')
    .version('1.0.0')
    .argument('<action>', 'what to do: build or deploy')
    .option('-w, --watch', 'rebuild on file changes (only for build)')
    .action((action, opts) => {
        // ...
    })

program.parse(process.argv)
dotenv - Env Variables

import dotenv from 'dotenv' - dotenv()

# .env
API_KEY=abcd1234
DEBUG=true
  • cross-env
// package.json scripts
"scripts": {
  "start": "cross-env NODE_ENV=production node index.js"
}

Process & Child Processes

spawn
import {spawn} from 'child_process'

spawn('yarn', ['audit', '--json']).stdout.on('data', rawLine => {
    if (rawLine) {
        const item = JSON.parse(line)
        if (item?.type === 'auditSummary' && item.data?.vulnerabilities?.critical > 0) {
            console.error('Found critical vulnerability')
            process.exit(1)
        }
    }
})
Exit Codes
process.exit(1)
  • 1 - Success ✅ - to help memorize, think *"1 finger up 👍"
  • 0 - Failure ❌ - to help memorize, think *“0 trophies 🏆”
exec
import {execSync} from 'child_process'
execSync('yarn audit --json', {encoding: 'utf-8'})
  • Piping stdout / stderr
  • Signal handling (SIGINT, SIGTERM)
    • SIGINT: the “interrupt” signal (Ctrl+C in your terminal)
    • SIGTERM: the “terminate” signal (default kill command)
  • process.cwd()
Signals
  • SIGINT: the “interrupt” signal (Ctrl+C in your terminal)
  • SIGTERM: the “terminate” signal (default kill command)
process.on('SIGINT', () => {
    console.log('Caught SIGINT (Ctrl+C). Cleaning up...')
    process.exit(0)
})

process.on('SIGTERM', () => {
    console.log('Caught SIGTERM (kill). Cleaning up...')
    process.exit(0)
})

HTTP Client Requests

Native fetch Post
const res = await fetch(url)
const json = await res.json()

// POST JSON
await fetch(url, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({foo: 'bar'})
})
Cancel (Abort) fetch
const controller = new AbortController()
const {signal} = controller

// 2. Pass the signal to fetch
fetch('https://api.example.com/data', {signal})

controller.abort()
Fetch with retry
async function fetchWithRetry(url, options, retries = 3, delay = 1000) {
    try {
        return fetch(url, options)
    } catch (err) {
        if (retries === 0) throw err
        await new Promise(r => setTimeout(r, delay))
        return fetchWithRetry(url, options, retries - 1, delay * 2)
    }
}
Native fetch GET
const res = await fetch('https://api.example.com/data')
const data = await res.json()
Streaming downloads
import fs from 'fs'
const res = await fetch(fileUrl)
const dest = fs.createWriteStream('file.zip')
await new Promise((r, e) => res.body.pipe(dest).on('finish', r).on('error', e))

TypeScript in Node

Essential tsconfig flags
{
    "compilerOptions": {
        "target": "ES2020", // modern JS
        "module": "CommonJS", // or "ESNext" for ESM
        "strict": true, // all strict type-checks
        "esModuleInterop": true, // default imports from CJS
        "skipLibCheck": true, // faster builds
        "outDir": "dist", // compiled output
        "rootDir": "src", // source files
        "moduleResolution": "node" // resolve like Node.js
    }
}
Live reload tsx --watch
npm install -D tsx

# watch mode
npx tsx --watch src/index.ts
ES-module resolution in TS
{
    "compilerOptions": {
        "module": "ESNext",
        "moduleResolution": "NodeNext"
    }
}
"type": "module"
  • @types/node for built-ins
Live reload ts-node
# install once
npm install -D ts-node typescript

# run with auto-recompile on file change
npx ts-node-dev src/index.ts
Build → run dist/ output
npx tsc

node dist/index.js

Automation & Scheduling

node-cron schedules
import cron from 'node-cron'
//            ┌────────────── second (optional)
//            │ ┌──────────── minute
//            │ │ ┌────────── hour
//            │ │ │ ┌──────── day of month
//            │ │ │ │ ┌────── month
//            │ │ │ │ │ ┌──── day of week
//            │ │ │ │ │ │
//            │ │ │ │ │ │
//            * * * * * *
cron.schedule('* * * * * *', () => {
    console.log('running a task every minute')
})

Logging, Debugging, Safety Nets

Console vs structured logs (Pino)
import pino from 'pino'
const logger = pino({level: process.env.LOG_LEVEL || 'info'})
logger.info({user: 'alice'}, 'User logged in')
Inspector flag (--inspect)

node --inspect=127.0.0.1:9229 script.js

Or launch.json in VSCode

{
    "type": "node",
    "request": "attach",
    "name": "Attach to Script",
    "address": "localhost",
    "port": 9229
}
Colored output (chalk)
import chalk from 'chalk'
console.log(chalk.green('✅ OK'), 'Server started on port', chalk.blue(port))

Packaging & Distribution

Publish CLI to npm
{
    "name": "my-cli",
    "version": "1.0.0",
    "bin": {
        "my-cli": "./index.js"
    }
}
npm login
npm publish --access public
Semver & changelog basics
npm version patch   # 1.0.0 → 1.0.1
npm version minor   # 1.0.1 → 1.1.0
npm version major   # 1.1.0 → 2.0.0

Streams & Buffers

Readable stream
import {createReadStream} from 'fs'
const r = createReadStream('./input.txt', {encoding: 'utf8', highWaterMark: 64 * 1024})
r.on('data', chunk => console.log('got %d bytes', chunk.length))
r.on('end', () => console.log('finished'))
// Or: let buf; while (null !== (buf = r.read())) { … }
Pipe Chains (Simple chaining)
import {createReadStream, createWriteStream} from 'fs'
createReadStream('big.log').pipe(createWriteStream('copy.log'))
Custom Transform
import {Transform} from 'stream'
class Uppercase extends Transform {
    _transform(chunk, _, callback) {
        this.push(chunk.toString().toUpperCase())
        callback()
    }
}
// Usage:
process.stdin.pipe(new Uppercase()).pipe(process.stdout)
String -> Buffer
const buf = Buffer.from('✓ Success', 'utf8') // buffer to string is toString()
Writable stream
import {createWriteStream} from 'fs'
const w = createWriteStream('./output.txt', {flags: 'w'})
w.write('Hello') // returns false if buffer is full
w.end('World', () => console.log('done'))
w.on('drain', () => console.log('ready for more'))
Pipe chains (Transform)
import zlib from 'zlib'
createReadStream('app.log')
    .pipe(zlib.createGzip()) // transform
    .pipe(createWriteStream('app.log.gz'))
Real-World Example: Compress HTTP response with back-pressure control
import http from 'http'
import zlib from 'zlib'

http.createServer((req, res) => {
    const src = createReadStream('./large.json')
    res.writeHead(200, {'Content-Encoding': 'gzip'})
    const gzip = zlib.createGzip({highWaterMark: 32 * 1024})

    src.pipe(gzip) // transform + back-pressure
        .pipe(res, {end: true})
}).listen(3000)

Event Loop & Async Patterns

Micro-tasks/Macro-tasks
setTimeout(() => console.log('timeout'), 0) // macro-task (timers)
setImmediate(() => console.log('immediate')) // macro-task (check phase)
process.nextTick(() => console.log('nextTick')) // micro-task

Promise.resolve().then(() => console.log('promise')) // micro-task

console.log('end')
// Likely output:
// start
// end
// nextTick
// promise
// timeout      (or immediate first on some versions)
// immediate
Graceful shutdown
process.on('SIGTERM', async () => {
    console.log('Shutting down...')
    server.close() // stop accepting new connections
    // wait up to 5s for existing requests
    await wait(5000)
    console.log('Done')
    process.exit(0)
})
timers helpers
import {setTimeout as delay, setImmediate} from 'timers/promises'

// Delay for 500ms
await delay(500)

Or without timers helpers:

await new Promise(resolve => setTimeout(resolve, 500))

Testing & Coverage

Nodejs asserts/tests
  • Run test: node --test
import {test} from 'node:test'
import assert from 'node:assert'

const transform = data => {
    assert.strictEqual(typeof data, 'string')
}
Asserts:
assert.deepStrictEqual(cfg, {port: 3000})
assert.doesNotMatch('I will fail', /fail/, 'they match, sorry!')
assert.fail('boom')
assert.ifError(null) // throw if value is not null/undefined
assert.match('I will pass', /pass/)
assert.ok(typeof '123' === 'string')

Sample test:
import {test} from 'node:test'
import assert from 'node:assert/strict'

export function capitalize(str) {
    if (typeof str !== 'string') throw new TypeError('Expected a string')
    return str.charAt(0).toUpperCase() + str.slice(1)
}

test('capitalize uppercases first letter', () => {
    assert.strictEqual(capitalize('hello'), 'Hello')
})

test('capitalize throws on non-string', () => {
    assert.throws(() => capitalize(123), {
        name: 'TypeError',
        message: 'Expected a string'
    })
})

Live Reload & Watchers

nodemon
File watchers with chokidar
import {spawn} from 'child_process'
import chokidar from 'chokidar'

let proc
const start = () => {
    if (proc) proc.kill()
    proc = spawn('node', ['./src/index.js'], {stdio: 'inherit'})
}

chokidar.watch('./src/**/*.js').on('all', (evt, path) => {
    console.log(`${evt} detected on ${path}. Restarting…`)
    start()
})

start()
Production process manager
pm2 start app.js

https://www.npmjs.com/package/pm2


tsx --watch
Hot reload for ESM loaders
node --watch ./src/index.mjs

Concurrency

Spawning worker_threads
import {Worker} from 'worker_threads'

function runService(workerData) {
    return new Promise((resolve, reject) => {
        const worker = new Worker(new URL('./worker.js', import.meta.url), {workerData})
        worker
            .on('message', resolve)
            .on('error', reject)
            .on('exit', code => {
                if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`))
            })
    })
}

const result = await runService({fib: 40})
console.log(`Fibonacci result: ${result}`)
// worker.js
import {parentPort, workerData} from 'worker_threads'

function fib(n) {
    return n < 2 ? n : fib(n - 1) + fib(n - 2)
}

parentPort.postMessage(fib(workerData.fib))
MessagePort & Transferables
// parent.js
import {MessageChannel} from 'worker_threads'

const {port1, port2} = new MessageChannel()
const worker = new Worker(new URL('./worker.js', import.meta.url), {
    transferList: [port2],
    workerData: {port: port2}
})

port1.on('message', data => {
    console.log('Received buffer length:', data.byteLength)
})

const buf = new ArrayBuffer(1024 * 1024)
port1.postMessage(buf, [buf]) // zero-copy transfer
// worker.js
import {parentPort, workerData} from 'worker_threads'

const port = workerData.port
parentPort.unref() // allow process to exit if only this port is left

port.on('message', buf => {
    // process the ArrayBuffer directly
    port.postMessage(buf, [buf])
})
Threads with piscina
// install: npm install piscina
import Piscina from 'piscina'
import {resolve} from 'path'

const piscina = new Piscina({
    filename: resolve(__dirname, 'task.js'),
    maxThreads: 4
})

;(async () => {
    const results = await Promise.all([piscina.run({file: 'large.csv'}), piscina.run({file: 'report.json'})])
    console.log(results)
})()
Cluster - example
// cluster-server.js
import cluster from 'cluster'
import http from 'http'
import os from 'os'

if (cluster.isPrimary) {
    const cpus = os.cpus().length
    for (let i = 0; i < cpus; i++) cluster.fork()
    cluster.on('exit', worker => console.log(`Worker ${worker.process.pid} died, spawning a new one`))
} else {
    http.createServer((req, res) => {
        // simulate work
        res.end(`Handled by ${process.pid}`)
    }).listen(8000, () => console.log(`Worker ${process.pid} listening`))
}

Security & HTTPS Basics

HTTPS server & TLS certs
import https from 'https'
import fs from 'fs'
import express from 'express'

const app = express()
// Load key & cert (e.g. from Let's Encrypt)
const options = {
    key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
    cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
}

https.createServer(options, app).listen(443, () => console.log('HTTPS running on port 443'))

CLI / UX Enhancements

Colors (chalk)
console.log(chalk.green('✔ Build succeeded!'))
console.log(chalk.yellow('⚠ Deprecation warning:'))
console.log(chalk.red('✖ Build failed'))
Progress bars (cli-progress)
import {SingleBar, Presets} from 'cli-progress'

const bar = new SingleBar({format: 'Progress |{bar}| {percentage}%'}, Presets.shades_classic)
const total = files.length
bar.start(total, 0)

for (const [i, file] of files.entries()) {
    await upload(file)
    bar.update(i + 1)
}
bar.stop()
Spinners (ora)
import ora from 'ora'

const spinner = ora('Downloading assets...').start()
await downloadAssets()
spinner.succeed('Assets downloaded')
Tables (cli-table)
import Table from 'cli-table'

const table = new Table({head: ['Name', 'Status', 'Time(ms)']})
results.forEach(r => table.push([r.name, r.ok ? '✔' : '✖', r.time]))
console.log(table.toString())

Node Runtime Flags & Profiling

--inspect / --inspect-brk
node --inspect-brk my-script.js
# In Chrome: open chrome://inspect → “Open dedicated DevTools”
NODE_OPTIONS env tweaks
export NODE_OPTIONS="--trace-deprecation --inspect=9229"
npm test  # now runs with both flags automatically

Containerization & Serverless Deploy

Minimal Node Dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build   # e.g., transpile TS or bundle

# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/index.js"]
AWS Lambda handler basics
Vercel
// api/hello.js
export default async function handler(req) {
    return new Response(JSON.stringify({msg: 'Hi from Vercel Edge'}), {
        headers: {'Content-Type': 'application/json'}
    })
}
// vercel.json
{
    "functions": {"api/*.js": {"runtime": "edge"}}
}
Docker Compose
version: '3.8'
services:
    app:
        build: .
        volumes:
            - .:/app # live sync code
            - /app/node_modules
        ports:
            - '3000:3000'
        environment:
            NODE_ENV: development
        command: npm run dev # e.g., nodemon
Cloudflare Workers

Boot & Run Basics

Shebang + chmod executable
echo '#!/usr/bin/env node' > script.mjs && chmod +x script.mjs
#!/usr/bin/env node
console.log(2)
./script.mjs

nodejs
cheatsheet
automation
scripts