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:
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 leakFunctions: 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:
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}`
}Objects, destructuring & spread
These show up in nearly every Node file — pulling values out of config objects, request bodies, and module imports:
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' }Arrays and the methods you will live in
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
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 |
|---|---|---|
|
| Loose equality coerces — avoid |
|
| Strict equality, no coercion — prefer |
|
| A historical bug, baked in forever |
|
| The right way to test for an array |
|
| 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
const port = 3000
console.log(`Listening on port ${port}`)
const city = req?.user?.address?.city ?? 'unknown' // safe deep accessAsynchronous 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:
Callbacks Refresher — the original Node pattern.
Promises Refresher — composable async values.
async / await Refresher — synchronous-looking async code.