Phase 1 — Foundations lesson-0006

Modern JavaScript for Backend

The JavaScript patterns you'll use every single day in a Node.js backend. Master these and the rest becomes easy.

CommonJS vs ES Modules

Node.js supports two module systems. You need to know both because you'll encounter both:

CommonJS (CJS) — older

// Importing
const express = require('express');
const { Router } = require('express');

// Exporting
module.exports = { myFunc };
module.exports = myFunc;

ES Modules (ESM) — modern

// Importing
import express from 'express';
import { Router } from 'express';

// Exporting
export { myFunc };
export default myFunc;
Which to use?
Use ESM for new projects — add "type": "module" to package.json. Use CommonJS when working with older codebases or packages that don't support ESM yet. You'll recognise which is which by the import style.

async / await — The Backend Workhorse

Almost every backend operation is asynchronous — reading files, querying databases, calling external APIs. async/await makes this feel synchronous:

Callbacks (avoid)

fs.readFile('./data.json',
  (err, data) => {
    if (err) throw err;
    db.save(data, (err2) => {
      if (err2) throw err2;
      // "callback hell"
    });
  });

async/await (use this)

async function saveData() {
  const data = await fs.readFile(
    './data.json'
  );
  await db.save(data);
  // linear, readable, same result
}

Error Handling with async/await

When an awaited Promise rejects, it throws — wrap in try/catch:

async function getUser(id) {
  try {
    const user = await db.users.findById(id);
    if (!user) throw new Error('User not found');
    return user;
  } catch (err) {
    // Log and re-throw, or return a safe default
    console.error('getUser failed:', err.message);
    throw err; // let the caller handle it
  }
}

// Running multiple async operations in parallel
const [user, posts, comments] = await Promise.all([
  db.users.findById(id),
  db.posts.findByUser(id),
  db.comments.findByUser(id),
]);
// All three queries run simultaneously — much faster than sequential awaits

Destructuring

// Object destructuring — extract named properties
const { name, email, age = 0 } = req.body; // age defaults to 0 if missing

// Rename while destructuring
const { id: userId } = req.params; // userId = req.params.id

// Array destructuring
const [first, second, ...rest] = items;

// In function parameters
function createUser({ name, email, role = 'user' }) {
  // no need for req.body.name, req.body.email etc.
}

Spread & Rest

// Spread: expand an object or array
const updated = { ...existingUser, email: 'new@email.com' }; // merge/override
const allItems = [...list1, ...list2]; // merge arrays

// Rest: collect remaining args
function log(level, ...messages) { // messages is an array
  console.log(`[${level}]`, ...messages);
}

Optional Chaining & Nullish Coalescing

// Optional chaining — safe navigation through possibly-null objects
const city = user?.address?.city; // undefined if user or address is null/undefined
const first = arr?.[0];           // safe array access
const result = fn?.();            // only calls if fn is defined

// Nullish coalescing — default only for null/undefined (not 0 or '')
const port = process.env.PORT ?? 3000;   // use 3000 if PORT is unset
const name = user.name ?? 'Anonymous';  // not || which would catch '' too

Template Literals

// Multi-line strings and interpolation
const message = `
  Hello ${user.name},
  Your order #${order.id} has been ${order.status}.
  Total: $${order.total.toFixed(2)}
`;

// Tagged templates (used by SQL libraries like Prisma)
const result = await sql`SELECT * FROM users WHERE id = ${userId}`;
// The library safely parameterises ${userId} — no SQL injection risk

Array Methods You'll Use Constantly

const users = [
  { id: 1, name: 'Alice', active: true  },
  { id: 2, name: 'Bob',   active: false },
  { id: 3, name: 'Carol', active: true  },
];

users.filter(u => u.active);                     // [Alice, Carol]
users.map(u => ({ id: u.id, name: u.name }));    // strip active field
users.find(u => u.id === 2);                   // Bob
users.some(u => !u.active);                      // true
users.every(u => u.active);                      // false
users.reduce((acc, u) => acc + (u.active ? 1 : 0), 0); // 2

🧠 Check Your Understanding


Go Deeper

Primary source: MDN — How to use Promises

Also read: javascript.info — async/await — the clearest deep-dive available.

Ask your teacher: "Show me how Promise.all differs from sequential awaits in terms of performance." or "What does ?? do that || doesn't?"