Full Stack FDP

Full Stack Engineering with Next.js, NestJS & DevOps

Prepared by eduartha

A comprehensive enterprise engineering track covering modern Javascript, Next.js, backend architecture with NestJS, and DevOps.

JavaScript Essentials for Full Stack Dev

Learning Objectives

  • Explain the difference between var, let, and const in terms of scoping and hoisting.
  • Implement modern ES6+ array methods (map, filter, reduce) to manipulate data structures efficiently.
  • Compare traditional promise .then() chains with async/await syntax for handling asynchronous data.
  • Debug common scope and context (this) errors often encountered by beginners.
  • Refactor legacy JavaScript code into modern, destructured ES6 syntax.

Why This Matters

Imagine you are building a user dashboard that fetches live course prices, filters them by discounts, and renders them on the screen. If you use outdated JavaScript (var, raw loops, nested callbacks), your code becomes a fragile "spaghetti" mess that is incredibly difficult to debug when a variable leaks its scope or an API call fails silently. Mastering modern ES6+ JavaScript isn't just about writing shorter code—it's about writing predictable, error-resistant logic that forms the unbreakable foundation of enterprise Next.js and NestJS applications.

Concept Explanation

JavaScript was initially designed to add simple interactivity to web pages. Today, it powers heavy enterprise backends and complex frontend architectures. To bridge this gap, modern JavaScript (ES6 and beyond) introduced robust mechanisms:

1. Block Scoping (let and const) In physics, if you place a gas in a sealed container, it cannot leak out and affect the environment. Block scoping works the same way. Variables declared with let or const are trapped inside the nearest {} block (like an if statement or a for loop). The old var keyword ignored these blocks and leaked out, causing unpredictable state changes.

2. Asynchronous Execution (async/await) JavaScript is single-threaded. Imagine a chef in a kitchen alone. If they put a turkey in the oven (a slow API call) and stare at it until it's done, nothing else gets cooked. Instead, the chef sets a timer and chops vegetables while waiting. async/await is the timer. It allows the main thread to pause execution on a specific function while continuing to handle other tasks (like UI updates) until the data arrives.

3. Declarative Array Methods Instead of telling the computer how to loop through an array step-by-step (imperative), methods like .map() and .filter() tell the computer what you want (declarative). This prevents off-by-one errors and makes data transformation pipelines infinitely more readable.

Worked Example

Let's build a foundational data-fetching and processing utility for our Student Course Marketplace. We will simulate fetching raw course data from a database and formatting it for the frontend.

// File: utils/courseProcessor.js

// 1. Simulating an asynchronous database fetch
const fetchRawCourses = async () => {
  // Simulating network delay
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: 1, title: 'Next.js Mastery', price: 5000, isActive: true },
        { id: 2, title: 'Legacy jQuery', price: 1000, isActive: false },
        { id: 3, title: 'NestJS Backend', price: 6000, isActive: true }
      ]);
    }, 1000);
  });
};

// 2. Processing the data using ES6 features
const getActiveCourseCatalog = async () => {
  try {
    console.log("Fetching courses...");

    // Using await to pause execution until the promise resolves
    const rawCourses = await fetchRawCourses();

    // 3. Destructuring and Array Methods
    const formattedCatalog = rawCourses
      .filter(course => course.isActive) // Keep only active courses
      .map(({ id, title, price }) => ({ // Object destructuring in parameters
        courseId: `EDU-${id}`,
        displayName: title.toUpperCase(),
        // Adding 18% GST calculation
        finalPriceINR: price + (price * 0.18) 
      }));

    console.log("Processed Catalog:", formattedCatalog);
    return formattedCatalog;

  } catch (error) {
    console.error("Failed to load courses:", error);
  }
};

// Execute
getActiveCourseCatalog();

To run this locally: 1. Save the code in a file named courseProcessor.js. 2. Open your terminal and run: node courseProcessor.js

Expected Output:

Fetching courses...
Processed Catalog: [
  { courseId: 'EDU-1', displayName: 'NEXT.JS MASTERY', finalPriceINR: 5900 },
  { courseId: 'EDU-3', displayName: 'NESTJS BACKEND', finalPriceINR: 7080 }
]

Common Errors & Debugging

Error 1: ReferenceError: Cannot access 'data' before initialization - Cause: You tried to use a variable declared with let or const before the line where it is defined. Unlike var, they are not "hoisted" to the top of the scope with an undefined value. - Fix: Move the variable declaration above the line where you are trying to use it.

Error 2: TypeError: Cannot read properties of undefined (reading 'map') - Cause: You are calling .map() on a variable that is undefined, usually because an API call failed or you forgot to await the asynchronous data fetch. - Fix: Ensure the data is an array before mapping (e.g., data?.map(...)) and verify your await keywords.

Error 3: SyntaxError: await is only valid in async functions - Cause: You used the await keyword inside a normal function that is not prefixed with async. - Fix: Add the async keyword to the function declaration: const myFunc = async () => { ... }.

Try It Yourself

Exercise 1: Fix the Bug (Remember/Understand) Goal: Identify the scoping issue. Starter Code:

function getDiscount() {
  if (true) {
    let discount = 10;
  }
  return discount;
}
console.log(getDiscount());

Acceptance Criteria: The code should successfully return 10 without throwing a ReferenceError. (Time: 2 mins)

Exercise 2: Add Total Revenue (Apply) Goal: Extend the worked example. Task: Take the formattedCatalog array from the worked example and use the .reduce() method to calculate the total finalPriceINR of all active courses. Acceptance Criteria: Log the total sum to the console. (Time: 5 mins)

Exercise 3: Add Validation (Apply/Analyze) Goal: Handle edge cases. Task: Modify the fetchRawCourses function to occasionally reject the promise (simulate a network failure). Update the catch block in getActiveCourseCatalog to return a fallback array: [{ courseId: 'ERROR', displayName: 'System Offline', finalPriceINR: 0 }]. Acceptance Criteria: When the promise rejects, the app does not crash and returns the fallback data. (Time: 10 mins)

Exercise 4: Debug the API Call (Analyze/Evaluate) Goal: Fix the asynchronous logic. Broken Code:

async function fetchUser() { return { name: "Aarav" }; }
function greetUser() {
  const user = fetchUser();
  console.log("Welcome " + user.name);
}
greetUser();

Task: Explain in writing why this prints "Welcome undefined" and rewrite the code to fix it. (Time: 5 mins)

Exercise 5: Student Cart Feature (Create) Goal: Extend the Student Course Marketplace. Task: Write a new async function checkoutCart(cartItems) that: 1. Takes an array of course IDs. 2. Filters out any IDs that are not valid numbers. 3. Maps over the remaining IDs to return an array of receipt objects { receiptId: "REC-...", status: "PAID" }. Acceptance Criteria: Must use const, .filter(), .map(), and ES6 arrow functions. (Time: 15 mins)

Indian Industry Context

At Swiggy and Zomato, heavy array manipulations are happening in the client-side JavaScript every time you apply filters (e.g., "Veg Only", "Rating 4.0+"). They rely heavily on optimized .filter() and .map() chains to instantly update the UI without needing to ask the backend to re-sort the database. Globally, platforms like Netflix use exact ES6 destructuring and async patterns in their Node.js microservices to aggregate personalized movie recommendations from multiple downstream APIs concurrently.

Freelance & Earning Angle

Mastering ES6 array methods and async/await is the fastest way to start earning on Upwork or Fiverr as a "Frontend Bug Fixer". Clients frequently pay ₹2,000–₹5,000 for quick scripts that fetch data from a third-party API (like Shopify or WordPress), parse the JSON, and map it into a clean format for their custom dashboard. If you can confidently write .map() and async/await, you can easily take on these API integration micro-gigs.

MCQ Bank

Q1: What is the primary difference between let and var? A) let is globally scoped, var is block scoped. B) let is block scoped, var is function scoped. C) let cannot be reassigned, var can. D) There is no difference, they are aliases. Correct: B Bloom's Level: Remember Explanation: let is strictly bound to the nearest {} block, preventing variable leakage. var leaks out of blocks up to the function level, causing unpredictable bugs.

Q2: Which array method is best for creating a new array containing only elements that meet a specific condition? A) .map() B) .reduce() C) .filter() D) .forEach() Correct: C Bloom's Level: Understand Explanation: .filter() returns a new array with only the elements that pass the truth test provided. .map() transforms every element, and .forEach() doesn't return an array at all.

Q3: What will the following code output?

const prices = [100, 200, 300];
const updated = prices.map(p => p * 2);
console.log(prices[0]);

A) 200 B) 100 C) undefined D) Error Correct: B Bloom's Level: Apply Explanation: The .map() method returns a completely new array; it does not mutate (change) the original prices array. Therefore, prices[0] remains 100.

Q4: Look at the following destructuring syntax. What is the value of title?

const course = { id: 1, info: { title: "Next.js", duration: "2h" } };
const { info: { title } } = course;

A) undefined B) "Next.js" C) { title: "Next.js" } D) Error Correct: B Bloom's Level: Apply Explanation: Nested object destructuring correctly extracts the title property from the inner info object.

Q5: What happens if you forget the await keyword before an asynchronous function call? A) The program crashes immediately. B) The function executes synchronously. C) You receive a pending Promise object instead of the actual data. D) The main thread freezes until the data arrives. Correct: C Bloom's Level: Apply Explanation: Without await, JavaScript does not pause. It immediately returns the Promise object representing the future completion of the task, leaving you with an unfulfilled Promise rather than the data.

Q6: Why does this code throw an error?

const config = { theme: 'dark' };
config = { theme: 'light' };

A) config is not an object. B) You cannot reassign a variable declared with const. C) Object properties cannot be changed. D) theme is a reserved keyword. Correct: B Bloom's Level: Analyze Explanation: const prevents reassignment of the variable identifier. (Note: you can mutate the inner properties like config.theme = 'light', but you cannot reassign the whole object).

Q7: You need to aggregate an array of objects into a single total sum. Which approach is the most idiomatic in modern ES6? A) A for loop with a mutable let accumulator. B) .forEach() updating a global variable. C) .reduce() returning the final accumulated value. D) .map() followed by .filter(). Correct: C Bloom's Level: Evaluate Explanation: .reduce() is explicitly designed for accumulating array values into a single output without mutating outside state, making it the most declarative and predictable choice.

Q8: A junior developer complains that their this context is undefined inside a callback function. What is the best modern solution? A) Use .bind(this) on the callback. B) Change the callback to an ES6 Arrow Function. C) Assign const self = this outside the callback. D) Use a standard function() declaration. Correct: B Bloom's Level: Analyze Explanation: ES6 Arrow functions do not have their own this binding; they inherit this from the surrounding lexical scope. This is the cleanest and most modern way to solve context issues without messy .bind() workarounds.

Chapter Summary

  • Block Scoping: let and const trap variables inside {} blocks, preventing scope leaks.
  • Immutability: Use const by default to prevent accidental variable reassignment.
  • Declarative Arrays: .map(), .filter(), and .reduce() transform data cleanly without manual loops.
  • Destructuring: Easily extract specific properties from objects and arrays directly into variables.
  • Asynchronous Flow: async/await allows you to write pause-and-resume asynchronous code that looks and reads like standard synchronous code.

What's Next

Now that you have a firm grasp on the JavaScript foundations required to handle data arrays and asynchronous APIs, you are ready to apply these concepts in a strongly-typed environment. In the next chapter, 0.2 TypeScript Basics — Types & Interfaces at a Glance, we will upgrade your JavaScript with compile-time safety.

TypeScript Basics — Types & Interfaces at a Glance