A hands-on 4-chapter course. No theory overload: you build from Chapter 1, Day 1. AI is your co-pilot throughout.
Runtime, npm, first server: everything you need to move fast
Node.js is JavaScript running outside the browser. Before Node, JavaScript only ran inside Chrome, Firefox, Safari. Node changed that. Now you write JS that runs on servers, reads files, talks to databases, and powers APIs.
It uses the V8 engine (same engine Chrome uses) and runs on a single thread with a non-blocking event loop. This means it handles thousands of simultaneous connections without breaking a sweat.
Download Node.js: go to nodejs.org and download the LTS version (Long-Term Support). This includes npm automatically.
Verify installation: open your terminal and run:
node --version # should print v20.x.x or higher
npm --version # should print 10.x.x or higher
If you work with multiple Node versions, use nvm (Node Version Manager). It lets you switch between versions instantly: nvm install 20 && nvm use 20
Create a file called hello.js and run it with Node. No browser needed.
// Node.js has access to system resources the browser doesn't
const os = require('os');
console.log('Hello from Node.js!');
console.log('Platform:', os.platform());
console.log('CPU cores:', os.cpus().length);
console.log('Free memory:', Math.round(os.freemem() / 1024 / 1024), 'MB');
terminal
node hello.js
# Hello from Node.js!
# Platform: win32
# CPU cores: 8
# Free memory: 4096 MB
Node ships with a built-in http module. You don't need anything else to handle web requests.
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
const data = {
message: 'Node.js server is running',
url: req.url,
method: req.method,
timestamp: new Date().toISOString()
};
res.end(JSON.stringify(data, null, 2));
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
terminal
node server.js
# Server running at http://localhost:3000
# Open browser at http://localhost:3000 to see the JSON response
npm lets you install libraries written by others. Instead of building everything from scratch, you pull in proven code. Every Node project starts with:
terminalmkdir my-project && cd my-project
npm init -y # creates package.json with defaults
npm install express # installs Express (web framework)
npm install nodemon -D # installs as dev dependency (auto-restart on change)
Create a real REST endpoint that returns structured data.
index.jsconst express = require('express');
const app = express();
app.use(express.json());
const users = [
{ id: 1, name: 'Alice', role: 'Developer' },
{ id: 2, name: 'Bob', role: 'Designer' },
];
app.get('/api/users', (req, res) => {
res.json({ success: true, count: users.length, data: users });
});
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
user ? res.json(user) : res.status(404).json({ error: 'User not found' });
});
app.listen(3000, () => console.log('API ready at http://localhost:3000/api/users'));
AI Tip: Paste your server.js into Claude or ChatGPT and ask: "Add POST and DELETE routes for users with in-memory storage." Watch how fast it scaffolds the full CRUD logic.
JSX, state, hooks: the building blocks of every modern web UI
React is a JavaScript library for building user interfaces. It was created by Facebook in 2013 and is now the most used frontend library in the world. The core idea: break your UI into reusable components. Each component is a function that returns HTML-like syntax called JSX.
Instead of manually updating the DOM, React watches your data (state) and automatically re-renders only the parts that changed. This makes UIs fast and predictable.
Forget Create React App; it's outdated. Vite is the modern standard. It starts in under a second and builds 10x faster.
terminalnpm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
# App running at http://localhost:5173
JSX looks like HTML but it's actually JavaScript. React compiles it to React.createElement() calls automatically. You write the familiar HTML syntax, React handles the rest.
// A component is just a function that returns JSX
function Welcome({ name, role }) {
return (
<div className="card">
<h2>Hello, {name}!</h2>
<p>Role: <strong>{role}</strong></p>
{role === 'admin' && <button>Admin Panel</button>}
</div>
);
}
// Use it like an HTML tag; pass data as attributes (props)
function App() {
return (
<div>
<Welcome name="Alice" role="admin" />
<Welcome name="Bob" role="user" />
</div>
);
}
State is data that changes over time. When state updates, React re-renders the component automatically. You never touch the DOM directly.
Counter.jsximport { useState } from 'react';
function Counter() {
// [currentValue, functionToUpdateIt] = useState(initialValue)
const [count, setCount] = useState(0);
const [color, setColor] = useState('blue');
const increment = () => {
setCount(prev => prev + 1);
if (count >= 9) setColor('red');
};
return (
<div>
<h1 style={{ color }}>Count: {count}</h1>
<button onClick={increment}>+1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
export default Counter;
useEffect runs code after the component renders. It's where you fetch data, set up subscriptions, or interact with external APIs.
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Runs once when component mounts (empty dependency array [])
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => { setUsers(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);
}
AI Tip: Tell Claude: "Convert my UserList component to use async/await and add a search filter that updates as I type." This is a perfect AI task: it's a clear, contained refactor.
Build a real Express API and consume it from React
When people say "full-stack with Node + React" they mean: React runs in the browser and Node/Express runs on the server. React asks the server for data, the server reads a database and sends JSON back, React renders the result.
const express = require('express');
const cors = require('cors');
const app = express();
// CORS lets your React app (port 5173) talk to this server (port 3000)
app.use(cors());
app.use(express.json());
// In-memory data store (replace with a database later)
let tasks = [
{ id: 1, title: 'Learn Node.js', done: false },
{ id: 2, title: 'Learn React', done: false },
{ id: 3, title: 'Build full-stack app', done: false },
];
app.get('/api/tasks', (req, res) => res.json(tasks));
app.post('/api/tasks', (req, res) => {
const task = { id: Date.now(), title: req.body.title, done: false };
tasks.push(task);
res.status(201).json(task);
});
app.patch('/api/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === Number(req.params.id));
if (!task) return res.status(404).json({ error: 'Not found' });
task.done = !task.done;
res.json(task);
});
app.listen(3000, () => console.log('API on http://localhost:3000'));
terminal
npm install cors
node server/index.js
import { useState, useEffect } from 'react';
const API = 'http://localhost:3000/api';
export default function App() {
const [tasks, setTasks] = useState([]);
const [newTitle, setNewTitle] = useState('');
useEffect(() => {
fetch(`${API}/tasks`).then(r => r.json()).then(setTasks);
}, []);
const addTask = async () => {
if (!newTitle.trim()) return;
const res = await fetch(`${API}/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: newTitle }),
});
const task = await res.json();
setTasks(prev => [...prev, task]);
setNewTitle('');
};
const toggle = async (id) => {
const res = await fetch(`${API}/tasks/${id}`, { method: 'PATCH' });
const updated = await res.json();
setTasks(prev => prev.map(t => t.id === id ? updated : t));
};
return (
<div style={{ maxWidth: 500, margin: '2rem auto', fontFamily: 'sans-serif' }}>
<h1>Full-Stack Task List</h1>
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
<input
value={newTitle}
onChange={e => setNewTitle(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addTask()}
placeholder="New task..."
style={{ flex: 1, padding: '0.5rem' }}
/>
<button onClick={addTask}>Add</button>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{tasks.map(task => (
<li key={task.id}
onClick={() => toggle(task.id)}
style={{ padding: '0.75rem', cursor: 'pointer',
textDecoration: task.done ? 'line-through' : 'none',
opacity: task.done ? 0.5 : 1 }}>
{task.done ? '✅' : '⬜'} {task.title}
</li>
))}
</ul>
</div>
);
}
You now have a working full-stack app. Run the Express server in one terminal, Vite in another, and you have a live task manager talking across client and server.
Use Claude and ChatGPT as your pair programmer: scaffold, debug, refactor
AI doesn't replace learning; it accelerates it. Once you understand components, state, and APIs (Chapters 1-3), AI helps you move 5x faster by handling boilerplate, suggesting patterns, and explaining errors instantly.
Bad prompts get generic code. Good prompts give you exactly what you need. Here are the patterns that work best for Node.js and React development:
"Create a React component called ProductCard that accepts name, price, image, and inStock as props. Use Tailwind CSS classes. Show an 'Out of Stock' badge when inStock is false. Include TypeScript types."
"I'm getting this error in my React app: [paste the full error + stack trace]. Here's the component causing it: [paste component]. What's wrong and how do I fix it?"
"Refactor this Express route to use async/await and add proper error handling with try/catch. Also add input validation for the request body fields: [paste your route]."
"Explain this code to me like I'm just learning React. Walk through each line and explain why it's needed: [paste code]."
You've already built the base in Chapter 3. Now upgrade it with AI. Here's your AI task sequence:
Add persistence. Prompt: "Add a JSON file as a simple database for my Express tasks API. Read on startup, write on every change."
Add filtering. Prompt: "Add a filter bar to my React task list. Filter by: All, Active, Completed. Keep the logic in state."
Style it. Prompt: "Style my Task Manager with a dark theme using plain CSS. Cards, hover effects, smooth transitions."
Add auth (stretch). Prompt: "Add a simple JWT authentication to my Express API with login endpoint and protected task routes."
Always read AI-generated code before running it. AI makes mistakes: wrong method names, outdated APIs, missing edge cases. Use it to scaffold, then review and adapt. The more you code, the better you'll catch AI errors.
The fastest way to learn is to build. Take any idea (a bookmark manager, a weather dashboard, a budget tracker) and build it with Node + React + AI. Every real project teaches you 10x more than any tutorial.