NodeJSJavaScript Essentials for Node

JavaScript Essentials for Node

Node.js is JavaScript, so before the Node-specific deep dives let's lock down the language features you will use constantly on the server. This is a focused refresher with the server-side angle highlighted — if you have never seen JavaScript before, work through the JavaScript tutorial first, then return here.

Variables and scope

Use const by default, let when you must reassign, and avoid var. Both const and let are block-scoped and live in the "temporal dead zone" until declared, which prevents a whole class of bugs that var's function-scoping and hoisting allow:

JS
const MAX = 100        // cannot be reassigned (the binding, not the value)
let count = 0          // can be reassigned
count += 1

for (let i = 0; i < 3; i++) { /* i is scoped to this loop */ }
// console.log(i)  // ReferenceError — good, it does not leak
const freezes the binding, not the object
`const obj = {}` means you cannot point `obj` at a *different* object — but you can still `obj.x = 1`. To make the contents immutable too, use `Object.freeze(obj)` (shallow) or a deep-freeze for nested data.
Functions: declarations vs arrows

Arrow functions are concise and — crucially — do not have their own this or arguments. They inherit this from the surrounding scope, which is exactly what you want in most callbacks and exactly what bites you in object methods:

JS
function add(a, b) { return a + b }          // declaration (hoisted)
const multiply = (a, b) => a * b              // arrow, implicit return
const greet = (name) => {                     // arrow with a body
  return `Hello, ${name}`
}
Do not use an arrow as an object/prototype method that needs `this`
`{ value: 1, get() => this.value }` fails — the arrow's `this` is the *module*, not the object. Use a regular method (`get() { return this.value }`) when you need the receiver. Conversely, inside `setTimeout`/array callbacks, an arrow is perfect because it keeps the outer `this`.
Objects, destructuring & spread

These show up in nearly every Node file — pulling values out of config objects, request bodies, and module imports:

JS
const user = { id: 1, name: 'Ada', role: 'admin' }

// Destructuring with defaults and renaming
const { name, role = 'guest' } = user
const { id: userId } = user           // id → userId

// Rest — collect "the remaining" properties
const { id, ...rest } = user          // rest = { name, role }

// Spread — copy/merge objects (SHALLOW)
const updated = { ...user, role: 'editor' }
Spread and rest are shallow
`{ ...user }` copies top-level properties only — nested objects are still shared by reference. Mutating `copy.address.city` also changes `user.address.city`. For a true deep copy use the global `structuredClone(user)`.
Arrays and the methods you will live in

JS
const nums = [1, 2, 3, 4, 5]

nums.map((n) => n * 2)               // [2,4,6,8,10] — transform
nums.filter((n) => n % 2 === 0)      // [2,4]        — select
nums.reduce((sum, n) => sum + n, 0)  // 15           — aggregate
nums.find((n) => n > 3)              // 4            — first match
nums.some((n) => n > 4)              // true
nums.every((n) => n > 0)             // true
These do not help with async
`array.map(async fn)` returns an array of *promises*, not results — a classic trap. To run async work over an array, use `for...of` with `await` (sequential) or `await Promise.all(arr.map(async ...))` (parallel). Covered in [async / await Refresher](/nodejs/async-await-refresher).
Equality, types, and the falsy trap

Server code parses untrusted input, where loose equality and falsy values cause real bugs. Always use ===, and know the falsy set:

Expression

Result

Why

0 == false

true

Loose equality coerces — avoid

0 === false

false

Strict equality, no coercion — prefer

typeof null

'object'

A historical bug, baked in forever

Array.isArray([])

true

The right way to test for an array

[] == false

true

Coercion madness — never rely on it

The falsy values are: false, 0, -0, 0n, '', null, undefined, and NaN. Everything else — including '0', 'false', [], and {} — is truthy.

Template literals & optional chaining

JS
const port = 3000
console.log(`Listening on port ${port}`)

const city = req?.user?.address?.city ?? 'unknown'  // safe deep access
?? vs ||
`??` (nullish coalescing) only falls back on `null`/`undefined`, while `||` falls back on *any* falsy value (`0`, `''`, `false`). For defaults, `??` is usually what you want so you do not clobber a legitimate `0` or empty string — e.g. `const limit = input ?? 10` keeps a user-supplied `0`.
Asynchronous JavaScript — the big one

Node is built on async I/O, so you must be fluent in the three styles of asynchronous code. They are not three separate things — promises wrap callbacks, and async/await is syntax over promises. We dedicate a page to each:

Next
Start with [Callbacks Refresher](/nodejs/callbacks-refresher) — understanding callbacks explains *why* promises and async/await were invented.