Created by Mahmood Damra
px = fixed, doesn't scale rem = relative to html font-size (16px default) em = relative to parent (compounds! ⚠️)
Rule of thumb: • Font sizes: rem • Padding/margins: rem or em • Widths: % or fr • Never: px for fonts
html { font-size: 16px; }
h1 { font-size: 2.5rem; } /* 40px */
p { font-size: 1rem; } /* 16px */
.container { max-width: 75rem; } /* 1200px */Use Case: Scalable, accessible typography and spacing.
Flexbox shines on mobile-first designs.
flex-direction: column on mobile → row on desktop. flex-wrap: wrap lets items flow to next line. gap replaces margin hacks between items.
Combine with media queries for full control.
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
flex: 1 1 300px; /* grow, shrink, min-width */
}
@media (max-width: 640px) {
.cards { flex-direction: column; }
}Use Case: Card grids, navigation that stacks on mobile.
CSS functions for fluid responsive values.
clamp(min, preferred, max) — stays within bounds. min(a, b) — picks the smaller value. max(a, b) — picks the larger value.
No media queries needed!
/* Fluid font: 1.2rem min, scales with vw, 3rem max */
h1 { font-size: clamp(1.2rem, 4vw, 3rem); }
/* Container: 90% of viewport but max 1200px */
.container { width: min(90%, 75rem); margin: auto; }
/* At least 300px wide */
.sidebar { width: max(300px, 25%); }
Use Case: Fluid typography, responsive containers without breakpoints.
Dark mode is expected in modern design.
Use CSS custom properties for theming:
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a2e;
--accent: #3b82f6;
}
[data-theme='dark'] {
--bg-primary: #0f172a;
--text-primary: #e2e8f0;
--accent: #60a5fa;
}
body {
background: var(--bg-primary);
color: var(--text-primary);
}Color systems: • Use HSL for easy manipulation • 60-30-10 rule: dominant, secondary, accent • Semantic names: --color-success, --color-error • Check contrast ratios in both themes!
Use Case: Professional apps with theme switching.
Consistent spacing = professional look.
Use a spacing scale (4px-based or 8px-based): 4, 8, 12, 16, 24, 32, 48, 64, 96
Common layout patterns: • Sidebar + Main — grid: 250px 1fr • Holy Grail — header, sidebar, main, footer • Card Grid — auto-fit, minmax(280px, 1fr) • Split Screen — grid: 1fr 1fr • Stack — flex column with consistent gap
:root {
--space-xs: 0.25rem; --space-sm: 0.5rem;
--space-md: 1rem; --space-lg: 1.5rem;
--space-xl: 2rem; --space-2xl: 3rem;
}
.stack > * + * { margin-top: var(--space-md); }Use Case: Consistent, scalable design systems.
vw = 1% of viewport width vh = 1% of viewport height min(), max(), clamp() — responsive without media queries!
max-width prevents elements from growing too large. min-width prevents too small.
.hero { min-height: 100vh; }
.container { width: min(90%, 1200px); margin: auto; }
.title { font-size: clamp(1.5rem, 4vw, 3rem); }Use Case: Full-screen heroes, fluid typography, responsive containers.
Color palette — 1 primary, 1 accent, neutrals. 60-30-10 rule: 60% neutral, 30% primary, 10% accent.
Visual hierarchy — order in which eyes scan the page. Use size, color, position, contrast to control it.
Z-pattern (landing pages) or F-pattern (text-heavy pages).
Use Case: Making designs that look cohesive and guide user attention.
Mobile-first: base styles = mobile. @media (min-width: 768px) = tablet and up.
Can combine: @media (min-width: 768px) and (max-width: 1023px)
viewport meta tag is REQUIRED for mobile: <meta name='viewport' content='width=device-width, initial-scale=1'>
/* Mobile first (default) */
.grid { display: flex; flex-direction: column; }
/* Tablet */
@media (min-width: 768px) {
.grid { flex-direction: row; flex-wrap: wrap; }
}
/* Desktop */
@media (min-width: 1024px) {
.grid { max-width: 1200px; margin: auto; }
}
Use Case: Making layouts adapt from phone to desktop.
Images must adapt to screen sizes.
max-width: 100% — image never exceeds container. height: auto — maintain aspect ratio. object-fit: cover — fill container, crop if needed. object-fit: contain — fit inside, no crop.
<picture> element for art direction. srcset for resolution switching.
img { max-width: 100%; height: auto; }
.hero-img { object-fit: cover; width: 100%; height: 50vh; }<picture>
<source media='(min-width: 768px)' srcset='large.webp' />
<img src='small.webp' alt='Hero' />
</picture>
Use Case: Fast-loading, crisp images on all devices.
Contrast — make important elements stand out. Size, color, weight, position all create contrast.
Scale — create visual hierarchy with size. Big = important, small = secondary.
Typography — font choice sets the tone. Max 2-3 fonts. Pair serif + sans-serif.
Use Case: Creating designs that guide the user's eye naturally.
Hamburger menu on mobile, full nav on desktop.
Hide nav links by default on mobile. Toggle with JS classList.toggle('open'). Use display:none → display:flex in media queries.
.nav-links { display: none; flex-direction: column; }
.nav-links.open { display: flex; }
.hamburger { display: block; cursor: pointer; }
@media (min-width: 768px) {
.nav-links { display: flex; flex-direction: row; }
.hamburger { display: none; }
}hamburger.addEventListener('click', () => {
navLinks.classList.toggle('open');
});Use Case: Every mobile-responsive website needs this.
White space (negative space) lets content breathe. More white space = more premium feel.
Alignment creates visual order. Align elements to a grid or shared edges. Misalignment looks unprofessional.
Less clutter = better UX.
Use Case: The difference between amateur and professional design.
Relative units are key to fluid design.
px is fixed. Avoid for containers and typography. rem = relative to root font-size (good for accessibility). % = relative to parent container. vw / vh = 1% of viewport width / height.
html { font-size: 16px; }
h1 { font-size: 2rem; } /* 32px, scales if user zooms */
.hero { height: 100vh; } /* fills entire screen height */
.sidebar { width: 25%; } /* 25% of its parent div */Use Case: Typography that scales, layouts that adapt to ANY screen.
Apply CSS rules at specific screen sizes.
Mobile-first: start with mobile styles, add complexity for larger screens with min-width.
Common breakpoints: • 640px (small) • 768px (tablet) • 1024px (laptop) • 1280px (desktop)
express-session stores session data server-side.
Session cookie sent to browser automatically. req.session.userId — check if logged in.
Protected route middleware: redirect if not authenticated.
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));
// Protect route
function requireAuth(req, res, next) {
if (!req.session.userId) return res.redirect('/login');
next();
}
app.get('/dashboard', requireAuth, handler);Use Case: Protecting pages, maintaining login state.
2.5 hours · 13 lessons
Design principles that make interfaces beautiful AND usable.
No design tool needed — these are universal principles.
Good REST = predictable, consistent URLs.
Naming conventions: • Plural nouns: /users not /user • Nested resources: /users/123/posts • Filter with query strings: /users?role=admin • Versioning: /api/v1/users
Idempotency: • GET = safe, no side effects • PUT = same result if repeated • POST = NOT idempotent (creates new each time) • DELETE = idempotent
Pagination: /api/posts?page=2&limit=20
Response shape:
{
"data": [...],
"meta": { "total": 100, "page": 2, "limit": 20 },
"error": null
}Use Case: Designing APIs other devs enjoy using.
Validate BEFORE processing data.
Check: required fields, types, lengths, formats. Return clear error messages.
function validateUser(data) {
const errors = [];
if (!data.email) errors.push('Email is required');
if (!data.email?.includes('@'))
errors.push('Invalid email format');
if (!data.password) errors.push('Password is required');
if (data.password?.length < 8)
errors.push('Password must be 8+ characters');
return errors;
}
// In route:
app.post('/register', (req, res) => {
const errors = validateUser(req.body);
if (errors.length) {
return res.status(400).json({ errors });
}
// proceed with registration...
});Use Case: Registration, login, any form submission on the server.
4.6 hours · 54 lessons
Make websites work on all screens: phone → tablet → desktop.
Mobile-first approach. Media queries. CSS Grid.
Projects: Product Page, Learning Journal
JSON = JavaScript Object Notation
Rules: • Keys MUST be double-quoted strings • Values: string, number, boolean, null, object, array • No trailing commas • No comments • No undefined
JSON.stringify(obj) → JS object to JSON string JSON.parse(str) → JSON string to JS object JSON.stringify(obj, null, 2) → pretty print
const user = { name: 'Alice', age: 25 };
const json = JSON.stringify(user);
// '{"name":"Alice","age":25}'
const parsed = JSON.parse(json);
// { name: 'Alice', age: 25 }
// Deep clone trick
const clone = JSON.parse(JSON.stringify(original));Use Case: Every API request/response uses JSON.
Every request has: • URL — where to send it • Method — what to do (GET/POST/PUT/DELETE) • Headers — metadata (Content-Type, Authorization) • Body — data to send (POST/PUT only)
Every response has: • Status code — 200 OK, 404 Not Found, 500 Error • Headers — metadata • Body — the data
fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Post' })
});Use Case: Every API interaction follows this pattern.
Register: validate → hash password → store in DB. Login: find user → compare password hash → create session.
bcryptjs — hash & compare passwords. Never store plain-text passwords!
const bcrypt = require('bcryptjs');
// Register
const hash = await bcrypt.hash(password, 10);
db.run('INSERT INTO users (email, password) VALUES (?, ?)', [email, hash]);
// Login
const user = db.get('SELECT * FROM users WHERE email = ?', [email]);
const valid = await bcrypt.compare(password, user.password);
if (valid) req.session.userId = user.id;Use Case: User accounts, secure authentication.
Verifying a user is who they claim to be.
NEVER store plain-text passwords. Hash them with bcrypt before saving.
const bcrypt = require('bcrypt');
// On Registration:
const saltRounds = 10;
const hashedPassword = await bcrypt.hash('mySecret123!', saltRounds);
await db.createUser(username, hashedPassword);
// On Login:
const user = await db.getUser(username);
const match = await bcrypt.compare('mySecret123!', user.passwordHash);
if (match) generateToken(); else reject();Use Case: Login forms, protecting user data, generating session tokens.
Endpoint = specific URL for a resource. Path params: /users/123 — identify specific item. Query strings: ?sort=date&limit=10 — filter/modify.
REST naming: nouns, not verbs. /users not /getUsers
// Path parameter
fetch(`/api/users/${userId}`)
// Query strings
fetch('/api/products?category=shoes&sort=price')
// Nested resources
fetch(`/api/users/${id}/orders`)
Use Case: Designing and consuming APIs with clean URL structures.
Express error middleware has 4 params: (err, req, res, next)
Must be defined AFTER all routes. Centralizes error responses.
next(err) inside any route passes to error handler.
// In routes — throw/pass errors:
app.get('/users/:id', async (req, res, next) => {
try {
const user = await db.getUser(req.params.id);
if (!user) {
const err = new Error('User not found');
err.status = 404;
return next(err);
}
res.json(user);
} catch (err) {
next(err); // pass to error handler
}
});
// Error handler (4 params!)
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
error: err.message || 'Internal Server Error'
});
});
Use Case: Clean, consistent error responses across all routes.
How the web communicates (Client <-> Server).
Request has a Method, URL, Headers, and Body. Methods:
GET: Read data (safe)POST: Create new dataPUT/PATCH: Update existing dataDELETE: Remove datafetch('https://api.github.com/users/bmad')
.then(res => res.json())
.then(data => console.log(data.avatar_url));Use Case: Every time a browser loads a page, or an app fetches data.
3.9 hours · 33 lessons
The most popular Node.js web framework. Simplifies routing, middleware, and server setup.
Projects: Startup Planet API, Fullstack Express App + Auth
Status codes tell you what happened.
2xx — Success: 200 OK · 201 Created · 204 No Content
3xx — Redirect: 301 Moved Permanently · 304 Not Modified
4xx — Client Error: 400 Bad Request · 401 Unauthorized 403 Forbidden · 404 Not Found · 422 Unprocessable
5xx — Server Error: 500 Internal · 502 Bad Gateway · 503 Unavailable
⚠️ fetch() does NOT throw on 404/500! You must check response.ok or response.status.
Use Case: Debugging API calls, proper error handling.
Functions that run BETWEEN request and response.
app.use(middleware) — applies to all routes. Run in order! Order matters.
Built-in: express.json() — parse JSON body express.static('public') — serve static files
// Parse JSON bodies
app.use(express.json());
// Serve static files
app.use(express.static('public'));
// Custom logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // pass to next middleware!
});
Use Case: Authentication, logging, body parsing, CORS, error handling.
Responsive Design · APIs · Node.js · Databases · Express · UI Design
From static pages to fullstack apps. Server-side dev, databases, and professional design.
7.6 hours · 96 lessons
APIs are the backbone of the web. Learn HTTP, REST, fetch, Promises, async/await.
Projects: BoredBot, BlogSpace, War Card Game, Dashboard Chrome Extension
.then() chains process data step by step. .catch() handles ANY error in the chain.
Promise.all() — run multiple requests in parallel. Promise.race() — first to complete wins.
Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([user, posts, comments]) => {
render(user, posts, comments);
})
.catch(err => showError(err));
Use Case: Fetching multiple resources simultaneously.
app.get('/path', (req, res) => {}) — handle GET requests.
Path params: /users/:id → req.params.id Query strings: ?sort=name → req.query.sort
express.Router() — modularize routes into separate files.
const router = express.Router();
router.get('/users', (req, res) => {
const { sort, limit } = req.query;
res.json(users);
});
router.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
Use Case: Building RESTful API endpoints.
The standard Node.js web server framework.
Turns raw Node.js into a clean routing system.
const express = require('express');
const app = express();
// Middleware to parse JSON bodies
app.use(express.json());
// A basic route
app.get('/api/health', (req, res) => {
res.status(200).json({ status: 'OK' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Use Case: Building REST APIs to serve data to Frontend apps.
Handling asynchronous actions gracefully.
Promises represent a future value (Pending -> Fulfilled / Rejected).
// Instead of nested callbacks, write flat chains:
function getUser(id) {
return new Promise((resolve, reject) => {
if (id < 1) reject(new Error('Invalid ID'));
setTimeout(() => resolve({ id, name: 'Viktor' }), 1000);
});
}
getUser(42)
.then(user => console.log(user.name))
.catch(err => console.error(err.message));
Use Case: Fetching from APIs, waiting for timers, reading server files.
2.9 hours · 50 lessons
Store, query, and manage data persistently.
SQL = Structured Query Language (relational data). Postgres, MySQL, SQLite — all use SQL.
SQL vs NoSQL, Managed vs Self-Hosted.
The modern standard for async code.
async function always returns a Promise. await pauses execution until Promise resolves. try/catch handles errors gracefully.
Check response.ok — fetch doesn't throw on 404!
async function loadDashboard() {
try {
const res = await fetch('/api/dashboard');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
renderDashboard(data);
} catch (err) {
showErrorMessage(err.message);
} finally {
hideLoader();
}
}Use Case: Production-quality data fetching with proper error handling.
Combining data spread across multiple tables.
Relational databases heavily utilize foreign keys.
-- INNER JOIN: Only rows that exist in BOTH tables
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id;
-- LEFT JOIN: ALL users, even if they have NO orders
SELECT users.name, COUNT(orders.id)
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.name;
Use Case: Getting a user's profile AND their past purchases in one query.
INNER JOIN — only matching rows from both tables. LEFT JOIN — all from left + matching from right. RIGHT JOIN — all from right + matching from left. FULL JOIN — all rows from both tables.
⚠️ SQL Injection: never concatenate user input into queries! Use parameterized queries.
SELECT users.name, orders.total
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.total > 50;
-- Parameterized (safe!)
-- db.get('SELECT * FROM users WHERE id = ?', [userId])
Use Case: Combining related data, preventing security vulnerabilities.
3.7 hours · 30 lessons
JavaScript outside the browser! Build servers, APIs, and fullstack apps.
Node uses the V8 engine. Non-blocking, event-driven.
Projects: Wild Horizons API, Fullstack Node App
Callbacks nest deeply → unreadable 'pyramid of doom'.
Promises flatten the chain. Each .then() returns a new Promise.
Callback hell:
getUser(id, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
// 3 levels deep already!
});
});
});Promise chain (flat!):
getUser(id)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => render(comments))
.catch(err => handleError(err));
Use Case: Understanding WHY promises exist.
Talking to relational databases.
The standard language for manipulating tables.
-- Create, Read, Update, Delete
INSERT INTO users (name, email) VALUES ('Alice', 'a@a.com');
SELECT id, name FROM users
WHERE email LIKE '%@gmail.com'
ORDER BY created_at DESC
LIMIT 10;
UPDATE users SET role = 'admin' WHERE id = 1;
DELETE FROM log_entries WHERE created_at < '2023-01-01';
Use Case: Querying Postgres, MySQL, or SQLite databases.
JavaScript outside the browser.
No window, no document. We have global and process instead. Access to the file system and network hardware.
// CommonJS imports (older standard, still common)
const fs = require('fs');
const path = require('path');
// Read a file synchronously
const data = fs.readFileSync(path.join(__dirname, 'data.txt'), 'utf8');
console.log(data);
Use Case: Building web servers, CLI tools, automated scripts.
Node's built-in http module creates servers.
http.createServer((req, res) => {...}) handles every request. req.url — the requested path. req.method — GET, POST, etc. res.writeHead(200, headers) — set status & headers. res.end(body) — send response.
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello World' }));
});
server.listen(3000);Use Case: Understanding how web servers work under the hood.
CREATE TABLE name (col type constraints)
Common types: INTEGER, TEXT, VARCHAR(n), BOOLEAN, TIMESTAMP
Constraints: PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT, REFERENCES
ALTER TABLE — add/remove columns.
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE users ADD COLUMN avatar_url TEXT;
Use Case: Designing your database schema.
Architecting your server code cleanly.
Don't put everything in server.js. Use the MVC pattern:
// MVC Folder structure
/controllers
userController.js
/models
User.js
/routes
userRoutes.js
server.js
Use Case: Scaling a backend codebase from 1 file to 100+ files without losing your mind.
Choosing the right type matters for performance & correctness.
Numeric: INTEGER — whole numbers REAL / FLOAT — decimals NUMERIC(10,2) — exact decimals (money!)
Text: TEXT — unlimited length VARCHAR(n) — max n characters CHAR(n) — fixed length
Date/Time: DATE — just date TIMESTAMP — date + time DEFAULT CURRENT_TIMESTAMP — auto-set
Other: BOOLEAN — true/false SERIAL / AUTOINCREMENT — auto-ID JSON / JSONB — store JSON (Postgres)
Use Case: Designing database schemas correctly.
Aggregate functions: COUNT, SUM, AVG, MAX, MIN
GROUP BY — group rows for aggregation. HAVING — filter groups (like WHERE for groups). ORDER BY — sort results.
SELECT country, COUNT(*) as user_count,
AVG(age) as avg_age
FROM users
GROUP BY country
HAVING COUNT(*) > 10
ORDER BY user_count DESC;
Use Case: Analytics, reports, dashboards, statistics.
SELECT columns FROM table WHERE condition
Filtering operators: =, !=, >, <, >=, <= LIKE '%pattern%' — partial match IN (val1, val2) — match any in list BETWEEN min AND max — range AND, OR, NOT — combine conditions
SELECT name, email FROM users
WHERE age >= 18
AND country IN ('US', 'UK')
AND name LIKE '%son'
ORDER BY name ASC
LIMIT 10;
Use Case: Fetching exactly the data you need.
Indexes speed up read queries dramatically.
CREATE INDEX idx_name ON table(column);
How it works: database creates a sorted lookup table. Like a book's index — find pages without reading everything.
⚠️ Trade-off: faster reads, slower writes. Only index columns you frequently search/filter/join on.
-- Speed up user lookups by email
CREATE INDEX idx_users_email ON users(email);
-- Composite index for frequent combined queries
CREATE INDEX idx_orders_user_date
ON orders(user_id, created_at);
-- Unique index (also enforces uniqueness)
CREATE UNIQUE INDEX idx_users_email_unique
ON users(email);
Use Case: Any table with thousands+ rows being searched.
npm = Node Package Manager
npm init -y — create package.json npm install pkg — add dependency npm install -D pkg — dev dependency npm run script — run a script npx command — run without installing
package.json scripts:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "vitest"
},
"dependencies": { "express": "^4.18.0" },
"devDependencies": { "nodemon": "^3.0.0" }
}^4.18.0 = compatible with 4.x.x ~4.18.0 = compatible with 4.18.x
node_modules/ — NEVER commit! Add to .gitignore
Use Case: Managing every Node.js project.
INSERT INTO table (cols) VALUES (vals) UPDATE table SET col=val WHERE condition DELETE FROM table WHERE condition
⚠️ Always use WHERE with UPDATE/DELETE! Without WHERE = affects ALL rows!
INSERT INTO users (name, email, age)
VALUES ('Alice', 'alice@mail.com', 25);
UPDATE users SET age = 26 WHERE id = 1;
DELETE FROM users WHERE id = 99;
Use Case: Creating, modifying, and removing records.
Routing = matching URL paths to handlers.
Manual routing checks req.url and req.method.
CORS (Cross-Origin Resource Sharing) — browsers block cross-origin requests by default. Server must send Access-Control-Allow-Origin header.
if (req.url === '/api/users' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
res.end(JSON.stringify(users));
}Use Case: Building APIs that frontend apps can consume.
SSE — server pushes data to client in real-time. One-way: server → client (simpler than WebSockets).
Nodemon — auto-restarts server on file changes. npx nodemon server.js — no manual restart!
// Server-Sent Events
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
Use Case: Live dashboards, notifications, real-time feeds.
fs — read/write files. path — handle file paths cross-platform.
fs.readFileSync — blocking (simple scripts) fs.readFile — non-blocking (servers) path.join() — safe path concatenation
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'data.json');
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
// Async version
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) throw err;
console.log(JSON.parse(data));
});Use Case: Reading config files, serving static assets, data storage.
Never hardcode secrets! Use env vars.
.env file stores config locally. dotenv package loads them into process.env.
Always add .env to .gitignore!
# .env
PORT=3000
DB_URL=postgres://user:pass@localhost/mydb
API_KEY=sk-abc123
SESSION_SECRET=mysupersecretkey
require('dotenv').config();
const port = process.env.PORT || 3000;
const dbUrl = process.env.DB_URL;
const apiKey = process.env.API_KEY;
// process.env values are always strings!
const maxRetries = parseInt(process.env.MAX_RETRIES || '3');Use Case: API keys, database URLs, port numbers, secrets.
POST/PUT requests send data in the body. Node receives it in chunks — must collect and parse.
req.on('data', chunk) — receive chunks. req.on('end') — all data received. JSON.parse(body) — convert to object.
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
const data = JSON.parse(body);
// sanitize & process data
res.writeHead(201);
res.end(JSON.stringify({ success: true }));
});
Use Case: Handling form submissions, creating resources.
EventEmitter — Node's pub/sub system.
emitter.on('event', callback) — listen emitter.emit('event', data) — trigger
Many Node objects extend EventEmitter: servers, streams, processes.
Streams — process data in chunks (not all at once). Readable, Writable, Transform, Duplex.
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('newOrder', (order) => {
console.log(`New order: ${order.id}`);
// notify warehouse, send email, etc.
});
emitter.emit('newOrder', { id: 123, item: 'Widget' });Use Case: Decoupled event-driven architecture, real-time features.
NEVER trust user input!
Types of attacks: • XSS — injecting <script> tags • SQL Injection — injecting SQL commands • Path Traversal — accessing ../../etc/passwd
Defense: • Escape HTML: replace < → < • Parameterized queries (never concatenate SQL) • Validate & whitelist input • Trim whitespace
function sanitize(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.trim();
}
// SQL: use ? placeholders
db.get('SELECT * FROM users WHERE id = ?', [userId]);Use Case: Every app that accepts user input.