Skip to content

nodejs - Node.js as a Language

Node.js is not a language , it's a runtime
Same V8 engine that runs Chrome , stripped of the DOM and window , glued to libuv for async I/O , wrapped in C++ bindings for filesystem access , network sockets , and process control. JavaScript in Node looks like browser JS but lives in a completely different world - no document , no fetch without a polyfill (well , now there is but you get the point) , and require() instead of <script> tags

the difference matters for security

In the browser , JS is sandboxed. DOM-based XSS steals cookies but can't read /etc/passwd. In Node , that same eval() becomes a full RCE with filesystem and network access under the process user. Same language , opposite threat model - and that's why this section exists

Node-specific JS patterns

CommonJS vs ESM:

// CommonJS - Node's original module system
const fs = require('fs')
const express = require('express')

// ESM - modern standard , same as browser
import fs from 'fs'
import express from 'express'
CJS uses require() / module.exports , ESM uses import / export. Node supports both but they don't always mix cleanly. "type": "module" in package.json flips the default

Process APIs:

// Environment - always validate , never trust
const dbPass = process.env.DB_PASSWORD // can be undefined , crash or leak

// Command-line args
const port = process.argv[2] || 3000 // always a string , sanitize it

// Process control
process.on('uncaughtException', (err) => {
  console.error('your server ate shit:', err.message)
  process.exit(1) // don't try to recover , just die clean
})

// Current working directory - can be exploited
process.cwd() // attackers might symlink here

File system basics:

import { readFileSync , writeFileSync , existsSync } from 'fs'

// Synchronous - blocks the event loop , fine for startup
const config = JSON.parse(readFileSync('./config.json', 'utf-8'))

// Asynchronous - preferred for request handlers
import { readFile } from 'fs/promises'
const data = await readFile('./data.txt', 'utf-8')

// path traversal check - non-negotiable
import { resolve , normalize } from 'path'
const safePath = normalize(resolve(baseDir, userInput))
if (!safePath.startsWith(baseDir)) throw new Error('path traversal detected')

Event loop awareness:

// This blocks EVERYTHING - one user ruins it for everyone
function heavySync() {
  for (let i = 0; i < 1e9; i++) JSON.parse('{}')
}

// This doesn't
import { setTimeout } from 'timers/promises'
async function heavyAsync() {
  for (let i = 0; i < 1e9; i++) {
    JSON.parse('{}')
    if (i % 1000 === 0) await setTimeout(0) // yield to event loop
  }
}

gotchas that will burn you

  • this behaves differently in CommonJS vs ESM modules
  • __dirname is undefined in ES modules - use import.meta.url instead
  • require.cache can be poisoned if you're not careful
  • Buffer is the Node equivalent of Uint8Array with extra methods
  • globalThis works everywhere but global is Node-only

quick reference index

Concept File in backend/node
Home / Intro node_00_home.md
Getting Started node_02_get_started.md
JS Requirements node_03_js_requirements.md
Node vs Browser node_04_vs_browser.md
V8 Engine node_06_v8_engine.md
Event Loop node_08_event_loop.md
Modules (CJS) mod_01_modules.md
ES Modules mod_02_es_modules.md
NPM mod_03_npm.md
package.json mod_04_package_json.md
File System core_01_fs.md
Path core_02_path.md
OS core_03_os.md
Events core_04_events.md
Buffers core_05_buffers.md
Streams core_06_streams.md
HTTP web_01_http.md
Async async_03_async_await.md
Error Handling async_04_error_handling.md
Security sec_01_owasp.md
Testing test_01_testing.md
Performance perf_01_profiling.md
Deployment deploy_01_env_setup.md

For the full Node.js backend curriculum , see Node.js Backend

Node is JavaScript with the training wheels off
Everything you learn about browser JS applies , but the attack surface is wider , the consequences are higher , and there's no try {} catch(e) {} for a production server that just crashed. Learn it right or learn it the hard way - your call