COURSE · 10 LESSONS · 100% FREE
⚛️

React & Node.js

Full-stack modern web development — 10 lessons covering JSX, hooks, Express, REST APIs, authentication, and deployment.

0Lessons
0Code Examples
HTML/CSS/JSPrerequisites
0%
You've completed 0 of 10 lessons
J/ Next
K/ Prev
Esc Collapse
/ Search
React Fundamentals

What is JSX?

JSX is a syntax extension for JavaScript that looks like HTML. It compiles to React.createElement() calls under the hood. Every JSX expression must return a single root element.

JSX
const element = <h1>Hello, React!</h1>;

const name = "Mayank";
const greeting = <p>Welcome, {name.toUpperCase()}!</p>;

// JSX must have a single parent — use fragments
const list = (
  <>
    <li>Item 1</li>
    <li>Item 2</li>
  </>
);

Function Components

Components are reusable pieces of UI. They are JavaScript functions that return JSX. Component names must start with a capital letter.

JSX
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const Card = ({ title, children }) => (
  <div className="card">
    <h2>{title}</h2>
    {children}
  </div>
);

function App() {
  return (
    <div className="app">
      <Welcome name="Mayank" />
      <Card title="React Basics">
        <p>Components make UI reusable.</p>
      </Card>
    </div>
  );
}

Props

Props are read-only inputs passed from parent to child. They flow one way — down the component tree. You can destructure them in the function signature.

JSX
function UserCard({ name, role = "Developer", avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <span>{role}</span>
    </div>
  );
}

<UserCard
  name="Mayank"
  role="Full-Stack Dev"
  avatar="/mayank.jpg"
/>

Conditional Rendering

JSX
function StatusBar({ isLoggedIn, username }) {
  if (!isLoggedIn) {
    return <p>Please log in.</p>;
  }

  return (
    <div>
      <p>Welcome back, {username}!</p>
      {username === "admin" && <span className="badge">Admin</span>}
    </div>
  );
}

Rendering Lists

Use Array.map() to render lists. Every list item needs a unique key prop for React to track changes efficiently.

JSX
function TodoList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.text} {item.done ? "✅" : "⬜"}
        </li>
      ))}
    </ul>
  );
}

const todos = [
  { id: 1, text: "Learn JSX", done: true },
  { id: 2, text: "Build components", done: false },
  { id: 3, text: "Master hooks", done: false },
];

<TodoList items={todos} />

Full Example — Counter App

JSX
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div className="counter">
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

export default Counter;
💡
JSX ≠ HTMLJSX uses className instead of class, htmlFor instead of for, and inline styles are objects (style={{ color: 'red' }}).

useState Hook

useState lets you add state to function components. It returns a pair: the current state value and a function to update it. State updates trigger re-renders.

JavaScript
import { useState } from "react";

function StateDemo() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("Mayank");

  const [user, setUser] = useState({ name: "Mayank", age: 15 });

  const birthday = () => {
    setUser((prev) => ({ ...prev, age: prev.age + 1 }));
  };

  const [items, setItems] = useState(["React", "Node.js"]);

  const addItem = (item) => {
    setItems((prev) => [...prev, item]);
  };

  return (
    <div>
      <p>{name} is {user.age} years old</p>
      <button onClick={birthday}>Happy Birthday</button>
      <ul>
        {items.map((item, i) => <li key={i}>{item}</li>)}
      </ul>
    </div>
  );
}
⚠️
Never mutate state directlyAlways use the setter function or spread operator: setItems([...items, newItem]), not items.push(newItem).

Event Handling

React events are named in camelCase and passed as JSX attributes. Always use functional updates when the new state depends on the previous state.

JavaScript
function EventDemo() {
  const [input, setInput] = useState("");
  const [submitted, setSubmitted] = useState([]);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    setSubmitted((prev) => [...prev, input]);
    setInput("");
  };

  const handleKeyDown = (e) => {
    if (e.key === "Escape") setInput("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder="Type something..."
      />
      <button type="submit">Add</button>
      <ul>
        {submitted.map((item, i) => (
          <li key={i}>{item}</li>
        ))}
      </ul>
    </form>
  );
}

Conditional Rendering

JavaScript
function Dashboard({ user, notifications }) {
  const hasNotifs = notifications.length > 0;

  return (
    <div>
      {user ? (
        <div className="dashboard">
          <h2>Welcome, {user.name}</h2>
          {hasNotifs ? (
            <ul className="notifications">
              {notifications.map((n, i) => (
                <li key={i}>{n.message}</li>
              ))}
            </ul>
          ) : (
            <p>No new notifications.</p>
          )}
        </div>
      ) : (
        <p>Please log in to view your dashboard.</p>
      )}
    </div>
  );
}

Lists with Filtering

JavaScript
function FilterList({ data }) {
  const [filter, setFilter] = useState("");

  const filtered = data.filter((item) =>
    item.name.toLowerCase().includes(filter.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items..."
      />
      <p>Showing {filtered.length} of {data.length} items</p>
      <ul>
        {filtered.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Lifting State Up

JavaScript
function TemperatureCalculator() {
  const [temp, setTemp] = useState("");
  const [scale, setScale] = useState("celsius");

  const celsius = scale === "fahrenheit"
    ? ((parseFloat(temp) - 32) * 5) / 9
    : parseFloat(temp);
  const fahrenheit = scale === "celsius"
    ? (parseFloat(temp) * 9) / 5 + 32
    : parseFloat(temp);

  return (
    <div>
      <TemperatureInput
        scale="celsius"
        value={isNaN(celsius) ? "" : celsius.toFixed(1)}
        onChange={setTemp}
        onScaleChange={setScale}
      />
      <TemperatureInput
        scale="fahrenheit"
        value={isNaN(fahrenheit) ? "" : fahrenheit.toFixed(1)}
        onChange={setTemp}
        onScaleChange={setScale}
      />
    </div>
  );
}
🧪 Quick Check
What does useState return?
← PrevReact Basics

useEffect

useEffect handles side effects: data fetching, subscriptions, timers, DOM manipulation. The dependency array controls when it runs.

JavaScript
import { useState, useEffect } from "react";

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      setLoading(true);
      try {
        const res = await fetch(`/api/users/${userId}`);
        const data = await res.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        console.error("Failed to fetch user:", err);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }

    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found.</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}
🚫
Missing dependency arrayIf you omit the dependency array, useEffect runs after every render. Always specify dependencies — or use [] for mount-only effects.

useRef

useRef creates a mutable ref that persists across renders. Common uses: accessing DOM elements, storing values that don't trigger re-renders.

JavaScript
import { useRef, useEffect } from "react";

function ChatInput({ onSend }) {
  const inputRef = useRef(null);
  const messageCount = useRef(0);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  const handleSend = () => {
    const text = inputRef.current.value;
    if (!text.trim()) return;
    messageCount.current += 1;
    onSend(text);
    inputRef.current.value = "";
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="Type a message..." />
      <button onClick={handleSend}>Send</button>
      <p>Messages sent: {messageCount.current}</p>
    </div>
  );
}

useContext

useContext lets you consume context without prop drilling. Create a context, provide it at the top, and consume it anywhere below.

JavaScript
import { createContext, useContext, useState } from "react";

const ThemeContext = createContext("light");

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  const toggleTheme = () => {
    setTheme((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme} className={`btn btn-${theme}`}>
      Current theme: {theme}
    </button>
  );
}

function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

Custom Hooks

Extract reusable logic into custom hooks — functions that start with use and can call other hooks.

JavaScript
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);

    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error(res.statusText);
        return res.json();
      })
      .then((json) => { if (!cancelled) setData(json); })
      .catch((err) => { if (!cancelled) setError(err); })
      .finally(() => { if (!cancelled) setLoading(false); });

    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

useReducer

JavaScript
import { useReducer } from "react";

const initialState = { items: [], total: 0 };

function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM":
      return {
        items: [...state.items, action.payload],
        total: state.total + action.payload.price,
      };
    case "REMOVE_ITEM":
      return {
        items: state.items.filter((_, i) => i !== action.index),
        total: state.total - state.items[action.index].price,
      };
    case "CLEAR":
      return initialState;
    default:
      return state;
  }
}

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  return (
    <div>
      <h2>Cart ({state.items.length}) — ${state.total.toFixed(2)}</h2>
      {state.items.map((item, i) => (
        <div key={i}>
          {item.name} ${item.price}
          <button onClick={() => dispatch({ type: "REMOVE_ITEM", index: i })}>
            Remove
          </button>
        </div>
      ))}
      <button onClick={() => dispatch({ type: "CLEAR" })}>Clear Cart</button>
    </div>
  );
}
← PrevState & Events

Controlled Inputs

In React, form inputs are controlled by state. The input value comes from state, and every change updates state.

JavaScript
function ControlledForm() {
  const [form, setForm] = useState({
    name: "", email: "", message: "",
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setForm((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Submitted:", form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" value={form.name}
        onChange={handleChange} placeholder="Name" required />
      <input type="email" name="email" value={form.email}
        onChange={handleChange} placeholder="Email" required />
      <textarea name="message" value={form.message}
        onChange={handleChange} placeholder="Message" rows={4} />
      <button type="submit">Send</button>
    </form>
  );
}

Form Validation

JavaScript
function ValidatedForm() {
  const [form, setForm] = useState({ email: "", password: "" });
  const [errors, setErrors] = useState({});

  const validate = () => {
    const errs = {};
    if (!form.email) errs.email = "Email is required";
    else if (!/\S+@\S+\.\S+/.test(form.email)) errs.email = "Invalid email";
    if (!form.password) errs.password = "Password is required";
    else if (form.password.length < 8) errs.password = "Min 8 characters";
    return errs;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const errs = validate();
    setErrors(errs);
    if (Object.keys(errs).length === 0) {
      console.log("Valid submission:", form);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input type="email" name="email" value={form.email}
          onChange={handleChange} placeholder="Email"
          className={errors.email ? "input-error" : ""} />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      <div>
        <input type="password" name="password" value={form.password}
          onChange={handleChange} placeholder="Password"
          className={errors.password ? "input-error" : ""} />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      <button type="submit">Register</button>
    </form>
  );
}

Multi-Step Form

JavaScript
function MultiStepForm() {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({
    firstName: "", lastName: "", email: "", phone: "",
  });

  const updateField = (field, value) => {
    setFormData((prev) => ({ ...prev, [field]: value }));
  };

  const next = () => setStep((s) => Math.min(s + 1, 3));
  const prev = () => setStep((s) => Math.max(s - 1, 1));

  return (
    <div className="multi-step">
      <div className="step-indicator">Step {step} of 3</div>

      {step === 1 && (
        <div>
          <input value={formData.firstName}
            onChange={(e) => updateField("firstName", e.target.value)}
            placeholder="First Name" />
          <input value={formData.lastName}
            onChange={(e) => updateField("lastName", e.target.value)}
            placeholder="Last Name" />
        </div>
      )}

      {step === 2 && (
        <div>
          <input value={formData.email}
            onChange={(e) => updateField("email", e.target.value)}
            placeholder="Email" />
          <input value={formData.phone}
            onChange={(e) => updateField("phone", e.target.value)}
            placeholder="Phone" />
        </div>
      )}

      {step === 3 && (
        <pre>{JSON.stringify(formData, null, 2)}</pre>
      )}

      <div>
        {step > 1 && <button onClick={prev}>Back</button>}
        {step < 3 && <button onClick={next}>Next</button>}
        {step === 3 && <button onClick={() => console.log(formData)}>Submit</button>}
      </div>
    </div>
  );
}

File Upload

JavaScript
function FileUpload() {
  const [file, setFile] = useState(null);
  const [preview, setPreview] = useState(null);

  const handleFile = (e) => {
    const selected = e.target.files[0];
    if (!selected) return;
    setFile(selected);

    if (selected.type.startsWith("image/")) {
      const reader = new FileReader();
      reader.onloadend = () => setPreview(reader.result);
      reader.readAsDataURL(selected);
    }
  };

  const upload = async () => {
    if (!file) return;
    const formData = new FormData();
    formData.append("file", file);

    const res = await fetch("/api/upload", {
      method: "POST",
      body: formData,
    });
    const data = await res.json();
    console.log("Uploaded:", data.url);
  };

  return (
    <div>
      <input type="file" onChange={handleFile} accept="image/*" />
      {preview && <img src={preview} alt="Preview" style={{ maxWidth: 200 }} />}
      {file && <p>{file.name} ({(file.size / 1024).toFixed(1)} KB)</p>}
      <button onClick={upload}>Upload</button>
    </div>
  );
}
Controlled vs UncontrolledControlled components let React own the state. Uncontrolled components let the DOM own it (use useRef). Controlled is preferred for most forms.
← PrevHooks Deep Dive

Basic Routing

JavaScript
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/contact">Contact</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

function Home() { return <h1>Home Page</h1>; }
function About() { return <h1>About Page</h1>; }
function Contact() { return <h1>Contact Page</h1>; }
function NotFound() { return <h1>404 — Not Found</h1>; }

Dynamic Routes with useParams

JavaScript
import { useParams, Link } from "react-router-dom";

const posts = [
  { id: "1", title: "React Basics", content: "JSX and components..." },
  { id: "2", title: "Node.js Intro", content: "Modules and fs..." },
  { id: "3", title: "Full-Stack Apps", content: "Connecting frontend..." },
];

function PostList() {
  return (
    <div>
      <h1>Blog Posts</h1>
      {posts.map((post) => (
        <div key={post.id}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </div>
      ))}
    </div>
  );
}

function PostDetail() {
  const { id } = useParams();
  const post = posts.find((p) => p.id === id);

  if (!post) return <p>Post not found.</p>;

  return (
    <article>
      <Link to="/posts">← Back to posts</Link>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Nested Routes

JavaScript
import { Outlet, useLocation } from "react-router-dom";

function DashboardLayout() {
  const location = useLocation();

  return (
    <div className="dashboard">
      <aside>
        <Link to="/dashboard"
          className={location.pathname === "/dashboard" ? "active" : ""}>
          Overview
        </Link>
        <Link to="/dashboard/settings">Settings</Link>
        <Link to="/dashboard/profile">Profile</Link>
      </aside>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<Overview />} />
  <Route path="settings" element={<Settings />} />
  <Route path="profile" element={<Profile />} />
</Route>

Programmatic Navigation with useNavigate

JavaScript
import { useNavigate } from "react-router-dom";

function LoginForm() {
  const navigate = useNavigate();
  const [error, setError] = useState("");

  const handleLogin = async (e) => {
    e.preventDefault();
    const res = await fetch("/api/login", { method: "POST", body: ... });
    if (res.ok) {
      navigate("/dashboard");
    } else {
      setError("Invalid credentials");
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input type="email" placeholder="Email" />
      <input type="password" placeholder="Password" />
      {error && <p className="error">{error}</p>}
      <button type="submit">Log In</button>
    </form>
  );
}

function BackButton() {
  const navigate = useNavigate();
  return <button onClick={() => navigate(-1)}>← Back</button>;
}

Protected Routes

JavaScript
import { Navigate, Outlet } from "react-router-dom";

function ProtectedRoute({ isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  return <Outlet />;
}

<Routes>
  <Route path="/login" element={<LoginForm />} />
  <Route element={<ProtectedRoute isAuthenticated={isLoggedIn} />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/profile" element={<Profile />} />
  </Route>
</Routes>

Query Parameters

JavaScript
import { useSearchParams } from "react-router-dom";

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const query = searchParams.get("q") || "";
  const page = parseInt(searchParams.get("page") || "1");

  const updateSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: "1" });
  };

  return (
    <div>
      <input value={query}
        onChange={(e) => updateSearch(e.target.value)}
        placeholder="Search..." />
      <p>Searching for: {query} (page {page})</p>
    </div>
  );
}
ℹ️
React Router v6+The latest version uses <Routes> instead of <Switch>, and element prop instead of component. Always check your version.
🧪 Quick Check
How do you access the :id parameter in a React Router v6 route?
← PrevForms & Validation
Node.js

Modules (CommonJS vs ES Modules)

JavaScript
// CommonJS (Node.js default)
const fs = require("fs");
const path = require("path");

// ES Modules — add "type": "module" in package.json
import fs from "fs";
import path from "path";

// Exporting
// CommonJS
module.exports = { helper1, helper2 };

// ES Modules
export function helper1() {}
export const helper2 = "value";

File System (fs)

JavaScript
import fs from "fs";
import { readFile, writeFile, readdir } from "fs/promises";

// Sync (blocks event loop)
const data = fs.readFileSync("data.json", "utf-8");
const parsed = JSON.parse(data);

// Async with promises
async function readDirectory(dirPath) {
  try {
    const entries = await readdir(dirPath, { withFileTypes: true });
    for (const entry of entries) {
      console.log(`${entry.isDirectory() ? "📁" : "📄"} ${entry.name}`);
    }
  } catch (err) {
    console.error("Cannot read directory:", err.message);
  }
}

async function copyFile(src, dest) {
  const content = await readFile(src);
  await writeFile(dest, content);
  console.log(`Copied ${src} → ${dest}`);
}

fs.mkdirSync("src/components", { recursive: true });

// Stream large files
import { createReadStream, createWriteStream } from "fs";

function streamCopy(src, dest) {
  const readStream = createReadStream(src);
  const writeStream = createWriteStream(dest);
  readStream.pipe(writeStream);

  writeStream.on("finish", () => console.log("Done!"));
  writeStream.on("error", (err) => console.error("Error:", err));
}
⚠️
Avoid sync methodsfs.readFileSync blocks the entire event loop. Always prefer async/await or streams for production code.

HTTP Server

JavaScript
import http from "http";

const server = http.createServer((req, res) => {
  const { method, url } = req;

  if (method === "GET" && url === "/") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("<h1>Home Page</h1>");
  } else if (method === "GET" && url === "/api/data") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: "Hello from Node.js" }));
  } else if (method === "POST" && url === "/api/echo") {
    let body = "";
    req.on("data", (chunk) => { body += chunk; });
    req.on("end", () => {
      res.writeHead(201, { "Content-Type": "application/json" });
      res.end(body);
    });
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Not Found");
  }
});

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

Path Module

JavaScript
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

path.join("/home", "user", "docs", "file.txt");
// → "/home/user/docs/file.txt"

path.resolve("src", "components", "App.js");
// → Absolute path to App.js from cwd

path.extname("document.pdf");      // → ".pdf"
path.basename("/home/user/app.js"); // → "app.js"
path.dirname("/home/user/app.js");  // → "/home/user"

path.isAbsolute("/home/user"); // → true
path.isAbsolute("src");        // → false

Events (EventEmitter)

JavaScript
import { EventEmitter } from "events";

class TaskRunner extends EventEmitter {
  constructor(tasks) {
    super();
    this.tasks = tasks;
    this.completed = 0;
  }

  run() {
    this.emit("start", this.tasks.length);
    for (const task of this.tasks) {
      this.executeTask(task);
    }
  }

  executeTask(task) {
    setTimeout(() => {
      this.completed++;
      this.emit("progress", this.completed, this.tasks.length);
      if (this.completed === this.tasks.length) {
        this.emit("done");
      }
    }, Math.random() * 1000);
  }
}

const runner = new TaskRunner(["Install deps", "Build", "Test", "Deploy"]);

runner.on("start", (count) => console.log(`Starting ${count} tasks...`));
runner.on("progress", (done, total) => console.log(`${done}/${total} complete`));
runner.on("done", () => console.log("All tasks finished!"));

runner.run();

Buffers and Streams

JavaScript
const buf = Buffer.from("Hello, Node.js!");
console.log(buf.toString());       // "Hello, Node.js!"
console.log(buf.toString("hex"));  // "48656c6c6f..."
console.log(buf.length);           // 15

const buf2 = Buffer.from([72, 101, 108, 108, 111]);
console.log(buf2.toString()); // "Hello"

import { Transform } from "stream";

const upperCaseTransform = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  },
});

process.stdin
  .pipe(upperCaseTransform)
  .pipe(process.stdout);

Process and Environment

JavaScript
console.log(process.env.NODE_ENV);      // "development"
console.log(process.env.PORT || 3000);

console.log(process.argv);
// ["node", "server.js", "--port", "8080"]

console.log(process.cwd());

const used = process.memoryUsage();
console.log(`${(used.heapUsed / 1024 / 1024).toFixed(2)} MB heap used`);

process.on("SIGTERM", () => {
  console.log("SIGTERM received. Shutting down gracefully...");
  server.close(() => process.exit(0));
});
← PrevReact Router
Backend & APIs

Setup & Basic Server

JavaScript
import express from "express";

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use((req, res, next) => {
  req.requestTime = new Date().toISOString();
  console.log(`${req.method} ${req.url} at ${req.requestTime}`);
  next();
});

app.get("/", (req, res) => {
  res.json({ message: "API is running", time: req.requestTime });
});

app.listen(PORT, () => {
  console.log(`Express server on port ${PORT}`);
});

Route Parameters & Query Strings

JavaScript
app.get("/users/:id", (req, res) => {
  const { id } = req.params;
  const { fields } = req.query;

  res.json({
    userId: id,
    requestedFields: fields ? fields.split(",") : [],
  });
});

app.get("/posts/:postId/comments/:commentId", (req, res) => {
  const { postId, commentId } = req.params;
  res.json({ postId, commentId });
});

import { Router } from "express";

const userRouter = Router();

userRouter.get("/", (req, res) => {
  res.json({ users: ["Alice", "Bob"] });
});

userRouter.get("/:id", (req, res) => {
  res.json({ user: { id: req.params.id } });
});

userRouter.post("/", (req, res) => {
  res.status(201).json({ created: req.body });
});

app.use("/api/users", userRouter);

Middleware

JavaScript
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ error: "Invalid token" });
  }
}

function requireRole(role) {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }
    next();
  };
}

app.get("/admin/users", authMiddleware, requireRole("admin"), (req, res) => {
  res.json({ users: getAllUsers() });
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: err.message || "Internal Server Error",
  });
});

Error Handling

JavaScript
function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

app.get("/api/users/:id", asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    const err = new Error("User not found");
    err.status = 404;
    throw err;
  }
  res.json(user);
}));

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

app.use((err, req, res, next) => {
  const status = err.statusCode || 500;
  res.status(status).json({
    error: err.message,
    ...(process.env.NODE_ENV === "development" && { stack: err.stack }),
  });
});
← PrevNode.js Basics

RESTful Design

JavaScript
import express from "express";
const router = express.Router();

let posts = [
  { id: 1, title: "First Post", body: "Hello world", createdAt: new Date() },
];
let nextId = 2;

// GET /api/posts — list all
router.get("/", (req, res) => {
  const { sort, limit } = req.query;
  let result = [...posts];

  if (sort === "newest") result.sort((a, b) => b.createdAt - a.createdAt);
  if (limit) result = result.slice(0, parseInt(limit));

  res.json({ count: result.length, data: result });
});

// GET /api/posts/:id — get one
router.get("/:id", (req, res) => {
  const post = posts.find((p) => p.id === parseInt(req.params.id));
  if (!post) return res.status(404).json({ error: "Post not found" });
  res.json(post);
});

// POST /api/posts — create
router.post("/", (req, res) => {
  const { title, body } = req.body;
  if (!title || !body) {
    return res.status(400).json({ error: "title and body are required" });
  }
  const newPost = { id: nextId++, title, body, createdAt: new Date() };
  posts.push(newPost);
  res.status(201).json(newPost);
});

// PUT /api/posts/:id — full replace
router.put("/:id", (req, res) => {
  const index = posts.findIndex((p) => p.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: "Post not found" });

  const { title, body } = req.body;
  if (!title || !body) {
    return res.status(400).json({ error: "title and body are required" });
  }

  posts[index] = { ...posts[index], title, body, updatedAt: new Date() };
  res.json(posts[index]);
});

// DELETE /api/posts/:id
router.delete("/:id", (req, res) => {
  const index = posts.findIndex((p) => p.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: "Post not found" });

  const deleted = posts.splice(index, 1)[0];
  res.json({ message: "Deleted", deleted });
});

export default router;

HTTP Status Codes

JavaScript
// 2xx Success
res.status(200).json(data);          // OK
res.status(201).json(created);       // Created
res.status(204).send();              // No Content

// 4xx Client Error
res.status(400).json({ error: "Bad request" });
res.status(401).json({ error: "Unauthorized" });
res.status(403).json({ error: "Forbidden" });
res.status(404).json({ error: "Not found" });
res.status(409).json({ error: "Conflict" });
res.status(422).json({ error: "Validation failed" });
res.status(429).json({ error: "Too many requests" });

// 5xx Server Error
res.status(500).json({ error: "Internal server error" });
res.status(502).json({ error: "Bad gateway" });
res.status(503).json({ error: "Service unavailable" });

Pagination

JavaScript
router.get("/", async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const skip = (page - 1) * limit;

  const total = await Post.countDocuments();
  const posts = await Post.find()
    .skip(skip)
    .limit(limit)
    .sort({ createdAt: -1 });

  res.json({
    data: posts,
    pagination: {
      page, limit, total,
      pages: Math.ceil(total / limit),
    },
  });
});

Input Sanitization

JavaScript
import mongoSanitize from "express-mongo-sanitize";

app.use(mongoSanitize());

function sanitizeInput(input) {
  if (typeof input === "string") {
    return input.trim().replace(/<[^>]*>/g, "");
  }
  if (Array.isArray(input)) return input.map(sanitizeInput);
  if (typeof input === "object" && input !== null) {
    return Object.fromEntries(
      Object.entries(input).map(([k, v]) => [k, sanitizeInput(v)])
    );
  }
  return input;
}

router.post("/", (req, res) => {
  const clean = sanitizeInput(req.body);
});
REST naming conventionUse plural nouns for resources (/api/posts not /api/post), and HTTP methods for actions (GET, POST, PUT, DELETE).
← PrevExpress.js
Production

Password Hashing with bcrypt

JavaScript
import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;

async function hashPassword(plainPassword) {
  return await bcrypt.hash(plainPassword, SALT_ROUNDS);
}

async function verifyPassword(plainPassword, hashedPassword) {
  return await bcrypt.compare(plainPassword, hashedPassword);
}

async function registerUser(email, password) {
  const existingUser = await User.findOne({ email });
  if (existingUser) throw new AppError("Email already registered", 409);

  const hashedPw = await hashPassword(password);
  const user = await User.create({ email, password: hashedPw });
  return { id: user.id, email: user.email };
}

async function loginUser(email, password) {
  const user = await User.findOne({ email });
  if (!user) throw new AppError("Invalid credentials", 401);

  const valid = await verifyPassword(password, user.password);
  if (!valid) throw new AppError("Invalid credentials", 401);

  return user;
}
🚫
Never store plain text passwordsAlways hash with bcrypt (or argon2) before storing. Use a salt round of at least 12.

JWT Tokens

JavaScript
import jwt from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
const JWT_EXPIRES = "7d";

function generateToken(user) {
  return jwt.sign(
    { id: user.id, email: user.email, role: user.role },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRES }
  );
}

function verifyToken(token) {
  return jwt.verify(token, JWT_SECRET);
}

app.post("/api/auth/register", asyncHandler(async (req, res) => {
  const { email, password, name } = req.body;

  if (!email || !password) {
    throw new AppError("Email and password required", 400);
  }

  const hashedPw = await bcrypt.hash(password, 12);
  const user = await User.create({ email, password: hashedPw, name });
  const token = generateToken(user);

  res.status(201).json({
    user: { id: user.id, email: user.email, name: user.name },
    token,
  });
}));

app.post("/api/auth/login", asyncHandler(async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    throw new AppError("Invalid email or password", 401);
  }

  const token = generateToken(user);

  res.json({
    user: { id: user.id, email: user.email, name: user.name },
    token,
  });
}));

Auth Middleware

JavaScript
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "No token provided" });
  }

  const token = authHeader.split(" ")[1];
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ error: "Invalid or expired token" });
  }
}

function authorize(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        error: `Role '${req.user.role}' is not authorized`,
      });
    }
    next();
  };
}

app.get("/api/profile", authenticate, (req, res) => {
  res.json({ user: req.user });
});

app.delete("/api/users/:id", authenticate, authorize("admin"), (req, res) => {
  // Only admin can delete users
});

Refresh Tokens

JavaScript
function generateTokens(user) {
  const accessToken = jwt.sign(
    { id: user.id, role: user.role },
    JWT_SECRET,
    { expiresIn: "15m" }
  );

  const refreshToken = jwt.sign(
    { id: user.id },
    REFRESH_SECRET,
    { expiresIn: "7d" }
  );

  return { accessToken, refreshToken };
}

app.post("/api/auth/login", asyncHandler(async (req, res) => {
  const user = await loginUser(req.body.email, req.body.password);
  const tokens = generateTokens(user);

  res.cookie("refreshToken", tokens.refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "strict",
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });

  res.json({ accessToken: tokens.accessToken });
}));

app.post("/api/auth/refresh", (req, res) => {
  const token = req.cookies.refreshToken;
  if (!token) return res.status(401).json({ error: "No refresh token" });

  try {
    const decoded = jwt.verify(token, REFRESH_SECRET);
    const accessToken = jwt.sign(
      { id: decoded.id },
      JWT_SECRET,
      { expiresIn: "15m" }
    );
    res.json({ accessToken });
  } catch (err) {
    res.status(403).json({ error: "Invalid refresh token" });
  }
});
← PrevREST APIs

Project Structure

Bash
fullstack-app/
├── client/
│   ├── src/
│   │   ├── components/
│   │   │   ├── LoginForm.jsx
│   │   │   ├── Dashboard.jsx
│   │   │   └── Navbar.jsx
│   │   ├── context/
│   │   │   └── AuthContext.jsx
│   │   ├── hooks/
│   │   │   └── useAuth.js
│   │   ├── App.jsx
│   │   └── main.jsx
│   ├── package.json
│   └── vite.config.js
├── server/
│   ├── middleware/
│   │   └── auth.js
│   ├── routes/
│   │   ├── auth.js
│   │   └── posts.js
│   ├── models/
│   │   └── User.js
│   ├── server.js
│   └── package.json
└── README.md

Express Backend

JavaScript
import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import authRoutes from "./routes/auth.js";
import postRoutes from "./routes/posts.js";

const app = express();
const PORT = process.env.PORT || 5000;

app.use(cors({
  origin: process.env.CLIENT_URL || "http://localhost:5173",
  credentials: true,
}));

app.use(express.json());

mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost/fullstack")
  .then(() => console.log("MongoDB connected"))
  .catch((err) => console.error("MongoDB error:", err));

app.use("/api/auth", authRoutes);
app.use("/api/posts", postRoutes);

app.get("/api/health", (req, res) => {
  res.json({ status: "ok", timestamp: new Date() });
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.statusCode || 500).json({
    error: err.message || "Internal Server Error",
  });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

React Frontend — Auth Context

JSX
import { createContext, useContext, useState, useEffect } from "react";

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(localStorage.getItem("token"));
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (token) {
      fetch("/api/auth/me", {
        headers: { Authorization: `Bearer ${token}` },
      })
        .then((res) => res.json())
        .then((data) => { if (data._id) setUser(data); })
        .catch(() => { localStorage.removeItem("token"); setToken(null); })
        .finally(() => setLoading(false));
    } else {
      setLoading(false);
    }
  }, [token]);

  const login = async (email, password) => {
    const res = await fetch("/api/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error);
    localStorage.setItem("token", data.token);
    setToken(data.token);
    setUser(data.user);
  };

  const logout = () => {
    localStorage.removeItem("token");
    setToken(null);
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, token, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used within AuthProvider");
  return ctx;
}

Vite Proxy & Deployment

JavaScript
// client/vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": "http://localhost:5000",
    },
  },
});

// Production: serve React build from Express
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

app.use(express.static(path.join(__dirname, "../client/dist")));

app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "../client/dist", "index.html"));
});

// .env (gitignored)
// MONGODB_URI=mongodb+srv://...
// JWT_SECRET=your-super-secret-key
// CLIENT_URL=https://yourdomain.com
// NODE_ENV=production

// Render / Railway deploy config
// Build: npm install && cd client && npm install && npm run build
// Start: npm start
💡
Deployment checklistNever commit .env files. Set environment variables on your hosting platform (Render, Railway, Vercel). Use NODE_ENV=production for optimized builds.
← PrevAuthentication

📚 Resources & Further Learning

⚛️
React Official Docs
The new React docs with hooks, patterns, and interactive examples.
react.dev →
🟢
Node.js Docs
Official Node.js documentation — API reference, guides, and best practices.
nodejs.org →
🚂
Express.js Guide
The fast, unopinionated web framework for Node.js.
expressjs.com →
📘
React Router Docs
Client-side routing for React — nested routes, loaders, actions.
reactrouter.com →
🔐
JWT.io
JSON Web Tokens — debug, verify, and learn about JWT tokens.
jwt.io →
📦
npm Registry
The world's largest software registry — discover packages for any project.
npmjs.com →
AI
React & Node.js Tutor
ZenMux · GLM 4.7 Flash
Ask me anything about React or Node.js! I can help with code examples, debugging, best practices, or explain any concept from the lessons above.