Skip to content

node_03 - JavaScript Requirements

Node runs JavaScript but not the JavaScript you're used to from the browser
If you're coming from frontend development , you know document.getElementById and fetch and addEventListener. None of that exists here. What you need is the core language: variables , functions , objects , arrays , callbacks , Promises. The DOM APIs are gone. The window object is gone. What remains is ECMAScript - the language spec itself , stripped of browser wrappers

what you need to know

These are non-negotiable fundamentals. If you don't know these , the rest of Node will make no sense and you'll be that person asking why document is undefined on Stack Overflow

variables - let , const , and the ghost of var

// const - can't reassign (but object properties can change)
const PI = 3.14159
// PI = 3  // TypeError: Assignment to constant variable

const user = { name: 'ali' }
user.name = 'mahmoud' // This works - const prevents reassignment, not mutation

// let - block-scoped , can reassign
let counter = 0
counter = 1 // fine

// var - avoid unless you like function-scoped confusion
// var leaks out of blocks
if (true) {
  var x = 10
}
console.log(x) // 10 - var is NOT block-scoped

arrow functions - the shorthand that changes this

// Traditional function
function add(a , b) {
  return a + b
}

// Arrow function - shorter , no own `this`
const add = (a , b) => a + b

// Arrow functions don't bind their own `this`
// This matters in callbacks and event handlers
const server = http.createServer((req , res) => {
  // Arrow function inherits `this` from surrounding scope
  // No .bind() needed , no `const self = this` hack
})

template literals - backticks save lives

const name = 'mahmoud'
const age = 27

// Old way - string concatenation nightmare
const old = 'Hello , my name is ' + name + ' and I am ' + age + ' years old'

// New way - template literals
const message = `Hello , my name is ${name} and I am ${age} years old`

Template literals also handle multiline strings without \n everywhere:

const html = `
  <div>
    <h1>${title}</h1>
    <p>${content}</p>
  </div>
`

destructuring - pulling data out of objects and arrays

const config = {
  host: 'localhost',
  port: 3000,
  ssl: false
}

// Old way
const host = config.host
const port = config.port

// Destructuring - clean , one line
const { host , port , ssl } = config

// Arrays too
const colors = ['red' , 'green' , 'blue']
const [first , second] = colors
// first = 'red', second = 'green'

spread operator - copy and merge without mutation

const defaults = { host: 'localhost' , port: 3000 }
const overrides = { port: 443 , ssl: true }

// Merge - later keys override earlier ones
const config = { ...defaults , ...overrides }
// { host: 'localhost', port: 443, ssl: true }

// Copy an array without reference
const original = [1 , 2 , 3]
const copy = [...original]
copy.push(4) // original unchanged

callbacks - the foundation of async Node

const fs = require('fs')

// Callback pattern - Node's original async primitive
// First argument is always an error (or null)
// Second argument is the result
fs.readFile('/etc/passwd' , 'utf8' , (err , data) => {
  if (err) {
    console.error('Failed to read file:' , err.message)
    return
  }
  console.log('File contents:' , data)
})

Every async function in Node uses either callbacks , Promises , or async/await. Know callbacks because you'll see them in older codebases and some core modules still use them

Promises - callbacks without the nesting

const fs = require('fs/promises')

// Promise-based API - no callback nesting
fs.readFile('/etc/passwd' , 'utf8')
  .then(data => console.log('File:' , data))
  .catch(err => console.error('Error:' , err.message))

// async/await - Promises but looks synchronous
async function readConfig() {
  try {
    const data = await fs.readFile('/etc/config.json' , 'utf8')
    return JSON.parse(data)
  } catch (err) {
    console.error('Config load failed:' , err.message)
    return {}
  }
}

what you do NOT need

These are browser APIs that don't exist in Node. Trying to use them will crash your process:

// These will throw ReferenceError in Node
// document.getElementById('app')       // not defined
// window.addEventListener('load', fn) // not defined
// localStorage.getItem('token')       // not defined
// fetch('/api/data')                  // actually works in Node 18+ (experimental)
// alert('hello')                      // definitely not defined

// What Node has instead:
// global       - analogous to window (but not the same)
// process      - process info and control
// require()    - module loading
// __dirname    - current file's directory

fetch is available in Node 18+ as an experimental feature (and stable in Node 21+), but it's built on Node's http module , not the browser's. Same API surface , different implementation under the hood

security note on eval and friends

The stuff you can do in Node that you can't in a browser includes eval , new Function() , and vm.runInThisContext(). These are dangerous in the browser. In Node , they're catastrophic:

// NEVER do this with untrusted input
const userInput = req.body.expression

// eval - full code execution
const result = eval(userInput) // RCE vector

// Function constructor - same thing , different syntax
const fn = new Function('return ' + userInput)
fn() // also RCE

// vm.runInThisContext - named misleadingly , NOT a sandbox
const vm = require('vm')
vm.runInThisContext(userInput) // has access to global , require , process

The vm module is explicitly documented as NOT a security boundary. You cannot run untrusted code safely in Node's default runtime. If you need to sandbox user code , use a separate process or a real sandbox (gVisor , Firecracker)

prerequisites

node_02 - Getting Started with Node


next -> node_04_vs_browser.md