Web Development, Exhaustively Explained
A complete, single-page reference for absolute beginners. Plain language, hundreds of working examples, and a glossary of every acronym and piece of jargon you will meet in your first year. Use the sidebar to jump to any subject.
How to use this Don't read it front to back. Skim the foundations, then jump to whatever you're building. Every section is self-contained. Come back to it as a lookup, not a novel.
How the Web Works
The web is a conversation between two computers. Your browser is the client. The machine holding the files is the server. Everything else is detail.
The request/response cycle
- You type a URL like
example.com/about. - DNS translates that human name into an IP address — the server's phone number.
- Your browser opens a TCP connection to that address (encrypted with TLS if the URL is HTTPS).
- The browser sends an HTTP request: "please give me /about", plus headers describing what it accepts.
- The server sends back an HTTP response: a status code, headers, and a body.
- The browser reads the HTML, requests the CSS and JavaScript it references, and paints the page.
The layers, from bottom to top
| Layer | What it does |
|---|---|
| Physical / Link | Cables, Wi‑Fi, Ethernet. Moves raw bits between machines. |
| IP | Addressing and routing. Gets a packet from one machine to another across networks. |
| TCP / UDP | TCP guarantees ordered, reliable delivery; UDP is faster but lossy (used for video, games). |
| TLS | Encrypts the connection. This is what the S in HTTPS means. |
| HTTP | The request/response protocol browsers and servers speak. |
| HTML / CSS / JS | What actually gets sent over HTTP and interpreted by the browser. |
Key idea Anything that runs in the browser is visible to the user. Never put passwords, API keys, or secrets in front-end code. That's what the back end is for.
The Three Core Languages
Every website you have ever visited is built from three languages working together.
| Language | Job | Analogy | File extension |
|---|---|---|---|
| HTML | Content and structure | The skeleton | .html |
| CSS | Appearance and layout | The skin and clothing | .css |
| JavaScript | Behaviour and interactivity | The muscles and nerves | .js |
You can build a perfectly good website with HTML and CSS alone. Add JavaScript only when something needs to respond to the user.
They connect like this
<!-- HTML: structure -->
<link rel="stylesheet" href="styles.css"> <!-- pulls in CSS -->
<script src="app.js"></script> <!-- pulls in JS -->
Setting Up Your Environment
You need three things to start: an editor, a browser, and a terminal.
1. Code editor — VS Code
Free, made by Microsoft, and the industry default for beginners. Install it, then add these extensions:
- Live Server — right-click an HTML file, open it in a browser, and it auto-reloads on save.
- Prettier — formats your code consistently on save.
- ESLint — flags JavaScript mistakes as you type.
- Path Intellisense — autocompletes file paths.
2. Browser — Chrome, Firefox, or Edge
All three have excellent built-in DevTools. Press F12
(or right-click → Inspect) to open them. You will live in here.
3. Terminal
A text interface to your computer. On macOS and Linux it's Terminal; on Windows it's PowerShell or Windows Terminal. The four commands you need first:
pwd # print working directory — where am I?
ls # list files (dir on Windows)
cd my-folder # change directory
mkdir new-dir # make a new directory
Folder structure for a simple site
my-site/
├── index.html ← the homepage (this name matters)
├── about.html
├── css/
│ └── styles.css
├── js/
│ └── app.js
└── images/
└── logo.png
Why index.html?
Web servers look for a file named index.html by default when
someone requests a directory. So example.com/ serves
example.com/index.html automatically.
URLs, HTTP & Status Codes
Anatomy of a URL
https://api.example.com:443/users/42?sort=name&limit=10#bio
└─┬─┘ └──────┬──────┘└┬─┘└───┬───┘└────────┬────────┘└─┬─┘
scheme host port path query string fragment
scheme → protocol (http, https, ftp, mailto)
host → the server's name
port → which door on that server (443 = HTTPS, 80 = HTTP)
path → which resource
query → extra parameters, after the ?
fragment → jump to a spot inside the page, after the #
Common HTTP methods
| Method | Meaning | Has a body? |
|---|---|---|
GET | Read something. Should never change data. | No |
POST | Create something. | Yes |
PUT | Replace something entirely. | Yes |
PATCH | Update part of something. | Yes |
DELETE | Remove something. | Usually no |
HEAD | Like GET but returns only headers. | No |
OPTIONS | Asks what methods are allowed (used by CORS). | No |
Status codes
| Range | Category | Common examples |
|---|---|---|
1xx | Informational | 101 Switching Protocols (WebSockets) |
2xx | Success | 200 OK, 201 Created, 204 No Content |
3xx | Redirect | 301 Moved Permanently, 304 Not Modified (cached) |
4xx | Client error | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests |
5xx | Server error | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
Common request and response headers
| Header | Direction | Meaning |
|---|---|---|
Content-Type | Both | What kind of data this is: text/html, application/json, image/png |
Accept | Request | What formats the client can handle |
Authorization | Request | Credentials, usually Bearer <token> |
Cookie | Request | Stored cookies for this domain |
Set-Cookie | Response | Tells the browser to store a cookie |
Cache-Control | Response | How long the browser may reuse this response |
Access-Control-Allow-Origin | Response | Which origins may read this response (CORS) |
Part II — HTML
HTML Basics
HTML is a markup language: you wrap content in tags to say what it is. Tags usually come in pairs — an opening tag and a closing tag — and together with their content they form an element.
<p>This is a paragraph.</p>
│ │ │ │
│ │ │ └─ closing tag
│ └─ content ────────┘
└─ opening tag
<!-- This whole line is a comment. Browsers ignore it. -->
Void (self-closing) elements
Some elements have no content and no closing tag:
<img src="cat.jpg" alt="A cat">
<br>
<hr>
<input type="text">
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
Nesting
Elements go inside other elements. Indentation is for humans — the browser doesn't care — but badly nested tags cause confusing bugs. Close them in reverse order of opening.
<!-- Correct: last opened, first closed -->
<p>Some <strong>bold</strong> text.</p>
<!-- Wrong: overlapping tags -->
<p>Some <strong>bold</p></strong>
Document Structure
index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A short summary for search engines.">
<title>My First Page</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
</head>
<body>
<h1>Hello, world!</h1>
<script src="app.js"></script>
</body>
</html>
| Part | Purpose |
|---|---|
<!DOCTYPE html> | Tells the browser to use modern standards mode. Always first line. |
<html lang="en"> | The root element. lang helps screen readers and translation tools. |
<head> | Metadata — nothing here is displayed on the page. |
<meta charset="UTF-8"> | Character encoding. Without it, accented characters and emoji break. |
<meta name="viewport"> | Makes the page respect mobile screen width. Essential. |
<title> | Text in the browser tab and in search results. |
<body> | Everything the user sees. |
Where to put <script>
Put scripts at the end of <body>, or add the
defer attribute. Otherwise the script runs before the HTML it
wants to manipulate exists.
<script src="app.js" defer></script>
Text & Inline Elements
Headings
<h1>Page title — use once per page</h1>
<h2>Major section</h2>
<h3>Subsection</h3>
<h4>Sub-subsection</h4>
<h5>Rarely used</h5>
<h6>Rarely used</h6>
Don't skip levels to get a specific font size — that's what CSS is for.
Paragraphs and breaks
<p>A paragraph of text.</p>
<p>Another paragraph.<br>A line break inside it.</p>
<hr> <!-- horizontal rule: a thematic break -->
Inline semantics
| Tag | Meaning | Looks like |
|---|---|---|
<strong> | Strong importance | bold |
<em> | Stress emphasis | italic |
<b> | Bold, no extra meaning | bold |
<i> | Italic, no extra meaning | italic |
<u> | Underline (looks like a link — avoid) | underlined |
<s> | No longer accurate | |
<mark> | Highlighted / relevant | marked |
<small> | Fine print | small |
<code> | Inline code | code |
<kbd> | Keyboard input | Ctrl |
<abbr> | Abbreviation | HTML |
<sub> / <sup> | Subscript / superscript | H2O, x2 |
<time> | A machine-readable date | <time datetime="2026-10-12"> |
Block vs inline
Block elements start on a new line and take full width
(<p>, <div>, <h1>).
Inline elements flow within text and only take the width they
need (<span>, <a>, <strong>).
Escaping special characters
| Character | Entity | When you need it |
|---|---|---|
< | < | To show a literal < |
> | > | To show a literal > |
& | & | To show a literal & |
" | " | Double quote inside an attribute |
| | A non-breaking space |
© | © | © |
Links & Images
Anchor links
<!-- Absolute URL: points to another site -->
<a href="https://example.com">Visit example.com</a>
<!-- Relative URL: points to a file in your project -->
<a href="about.html">About us</a>
<a href="pages/contact.html">Contact</a>
<a href="/">Home</a> <!-- leading / = site root -->
<!-- Open in a new tab (add rel for security) -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
New tab
</a>
<!-- Jump to a spot on the same page -->
<a href="#section-2">Go to section 2</a>
<h2 id="section-2">Section 2</h2>
<!-- Email and phone -->
<a href="mailto:hi@example.com">Email us</a>
<a href="tel:+15551234">Call us</a>
<!-- Download -->
<a href="report.pdf" download>Download the report</a>
Images
<img src="cat.jpg" alt="A tabby cat asleep on a keyboard" width="600" height="400">
<!-- Responsive: let the browser pick the best file -->
<img src="cat-small.jpg"
srcset="cat-small.jpg 480w, cat-medium.jpg 960w, cat-large.jpg 1440w"
sizes="(max-width: 600px) 480px, 960px"
alt="A tabby cat asleep on a keyboard">
<!-- With a caption -->
<figure>
<img src="chart.png" alt="Sales rising from 2020 to 2025">
<figcaption>Figure 1: Quarterly sales.</figcaption>
</figure>
<!-- Modern formats with fallback -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="A mountain at sunrise">
</picture>
Alt text rules
Describe the image's purpose, not its appearance. If the image is
purely decorative, use alt="" so screen readers skip it. Never
omit the attribute entirely.
Lists & Tables
Unordered list
<ul>
<li>Bread</li>
<li>Milk</li>
<li>Eggs</li>
</ul>
Ordered list
<ol>
<li>Preheat the oven</li>
<li>Mix the batter</li>
<li>Bake for 25 minutes</li>
</ol>
<!-- Start at a different number, or reverse -->
<ol start="5" reversed> … </ol>
Description list
<dl>
<dt>HTML</dt>
<dd>The language that structures a web page.</dd>
<dt>CSS</dt>
<dd>The language that styles it.</dd>
</dl>
Nested lists
<ul>
<li>Front end
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
</li>
<li>Back end</li>
</ul>
Tables
<table>
<caption>Monthly sales</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">January</th>
<td>$12,000</td>
</tr>
<tr>
<th scope="row">February</th>
<td>$15,500</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>$27,500</td>
</tr>
</tfoot>
</table>
Spanning cells:
<td colspan="2">Spans two columns</td>
<td rowspan="3">Spans three rows</td>
Tables are for data, not layout Before CSS Flexbox and Grid, people used tables to lay out whole pages. Never do this today. Screen readers announce tables as data, which makes a table-based layout nonsense to a blind user.
Forms & Inputs
Forms collect data from the user and send it somewhere.
A complete form<form action="/submit" method="post">
<!-- Text input with a proper label -->
<label for="name">Full name</label>
<input type="text" id="name" name="name" required placeholder="Ada Lovelace">
<!-- Email (browser validates the format) -->
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<!-- Password -->
<label for="pw">Password</label>
<input type="password" id="pw" name="password" minlength="8" required>
<!-- Number with range -->
<label for="age">Age</label>
<input type="number" id="age" name="age" min="0" max="120">
<!-- Date -->
<label for="dob">Date of birth</label>
<input type="date" id="dob" name="dob">
<!-- Checkbox -->
<input type="checkbox" id="news" name="newsletter" checked>
<label for="news">Subscribe to the newsletter</label>
<!-- Radio group (same name = one choice) -->
<fieldset>
<legend>Preferred contact</legend>
<input type="radio" id="c1" name="contact" value="email" checked>
<label for="c1">Email</label>
<input type="radio" id="c2" name="contact" value="phone">
<label for="c2">Phone</label>
</fieldset>
<!-- Dropdown -->
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Choose one…</option>
<option value="eg">Egypt</option>
<option value="us">United States</option>
</select>
<!-- Multi-line text -->
<label for="msg">Message</label>
<textarea id="msg" name="message" rows="5"></textarea>
<!-- File upload -->
<label for="file">Upload a file</label>
<input type="file" id="file" name="file" accept="image/*">
<button type="submit">Send</button>
</form>
Input types worth knowing
| Type | What it does |
|---|---|
text | Plain single-line text |
email | Validates email shape; mobile shows @ key |
password | Masks characters |
number | Numeric with spinners and min/max/step |
tel | Phone number; mobile shows numeric keypad |
url | Validates a URL |
date / time / datetime-local | Native date and time pickers |
range | A slider |
color | A colour picker |
search | Text field with a clear button on some browsers |
file | File picker |
checkbox | On/off, multiple can be checked |
radio | One of a group |
hidden | Not shown; sent with the form |
submit / reset / button | Button variants |
Validation attributes
<input required> <!-- must not be empty -->
<input minlength="3" maxlength="20"> <!-- text length -->
<input min="0" max="100"> <!-- numeric range -->
<input pattern="[0-9]{5}"> <!-- regex match -->
<input type="email"> <!-- format check -->
Always pair <label> with for
Clicking the label focuses the input, screen readers announce the input's name,
and it's a bigger tap target on mobile. The for attribute must
match the input's id exactly.
Semantic HTML
Semantic elements describe their content. They cost nothing, help screen readers, help search engines, and make your code readable to other humans.
| Element | Use for | Instead of |
|---|---|---|
<header> | Introductory content or navigation for a page or section | <div class="header"> |
<nav> | A block of navigation links | <div class="nav"> |
<main> | The primary content. One per page. | <div id="content"> |
<article> | Self-contained content that would make sense on its own (blog post, card) | <div class="post"> |
<section> | A thematic grouping, usually with a heading | <div class="section"> |
<aside> | Tangentially related content — sidebars, pull quotes | <div class="sidebar"> |
<footer> | Footer for a page or section | <div class="footer"> |
<figure> / <figcaption> | An image, diagram, or code sample with a caption | <div class="img-wrap"> |
<details> / <summary> | A native disclosure widget — no JavaScript needed | A JS accordion |
<dialog> | A native modal dialog | A styled <div> |
A typical page layout
<body>
<header>
<h1>My Site</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>Post title</h2>
<p>Post content…</p>
</article>
<aside>
<h3>Related</h3>
</aside>
</main>
<footer>
<p>© 2026 My Site</p>
</footer>
</body>
Native disclosure widget — no JavaScript
<details>
<summary>What is HTML?</summary>
<p>HTML is the language that structures a web page.</p>
</details>
What is HTML?
HTML is the language that structures a web page.
Accessibility
Roughly one in six people has a disability. Accessible HTML is not extra work — it's correct HTML.
The core checklist
- Every
<img>has a meaningfulalt(oralt=""if decorative). - Every form input has a
<label>tied byfor/id. - Headings descend in order: h1 → h2 → h3. No skipping.
- Colour is never the only way to convey information.
- Text contrast is at least 4.5:1 against its background.
- Everything interactive is reachable and usable with a keyboard alone.
- Links describe their destination: "Read the pricing guide", not "click here".
- Buttons are
<button>, links are<a>. Don't swap them.
Keyboard focus
/* Never do this without providing a replacement */
:focus { outline: none; } /* ✗ breaks keyboard navigation */
/* Do this instead */
:focus-visible {
outline: 3px solid currentColor;
outline-offset: 2px;
}
ARIA — when HTML isn't enough
ARIA attributes describe roles and states to assistive technology. The first rule of ARIA is don't use ARIA if a native element already does the job.
<!-- Unnecessary: <button> already announces itself -->
<button role="button">Save</button>
<!-- Necessary: a custom widget needs its state described -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden> … </ul>
<!-- Live region: screen readers announce changes -->
<p aria-live="polite">Saved.</p>
Skip link
<a href="#main" class="skip-link">Skip to content</a>
…
<main id="main"> … </main>
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
position: static;
display: inline-block;
padding: .5rem 1rem;
background: black;
color: white;
}
Attributes
Attributes live inside the opening tag as name="value".
Global attributes (work on any element)
| Attribute | Purpose |
|---|---|
id | A unique identifier on the page. Used by CSS, JS, and anchors. |
class | One or more space-separated labels for styling. |
style | Inline CSS. Avoid except for computed values. |
title | Tooltip text. Not reliably read on mobile. |
hidden | Hide the element entirely. |
lang | Language of the content. |
dir | Text direction: ltr or rtl. |
tabindex | Keyboard focus order. 0 = natural, -1 = focusable by script only, positive numbers = usually a mistake. |
contenteditable | Makes the element editable in the browser. |
data-* | Your own custom data. Read via dataset in JS. |
Data attributes
<!-- HTML -->
<div id="user" data-user-id="42" data-role="admin"></div>
<!-- JavaScript -->
const el = document.querySelector("#user");
console.log(el.dataset.userId); // "42" (kebab-case becomes camelCase)
console.log(el.dataset.role); // "admin"
id vs class
id | class | |
|---|---|---|
| How many per page | Must be unique | Reusable, many allowed |
| CSS selector | #name | .name |
| Specificity | High | Low |
| Typical use | Anchors, JS hooks, labels' for | Styling |
Part III — CSS
CSS Basics
CSS works by selecting elements and declaring how they should look. A rule has a selector and a block of declarations.
selector {
property: value;
another-property: value;
}
/* Real example */
p {
color: #333333;
line-height: 1.6;
margin-bottom: 1rem;
}
Three ways to add CSS
<!-- 1. External stylesheet — the right way -->
<link rel="stylesheet" href="styles.css">
<!-- 2. Internal — inside <style> in the <head> -->
<style>
p { color: red; }
</style>
<!-- 3. Inline — avoid except for computed values -->
<p style="color: red;">Text</p>
Comments
/* This is a CSS comment.
It can span multiple lines. */
Selectors
| Selector | Example | Selects |
|---|---|---|
| Universal | * | Every element |
| Type | p | Every <p> |
| Class | .card | Every element with class="card" |
| ID | #header | The one element with id="header" |
| Attribute | [disabled] | Elements with that attribute |
| Attribute value | [type="email"] | Elements with that exact value |
| Attribute starts with | [href^="https"] | Prefix match |
| Attribute ends with | [href$=".pdf"] | Suffix match |
| Attribute contains | [class*="btn"] | Substring match |
| Descendant | .card a | Links anywhere inside .card |
| Child | .card > a | Direct children only |
| Adjacent sibling | h2 + p | The <p> right after an <h2> |
| General sibling | h2 ~ p | All later siblings |
| Grouping | h1, h2, h3 | All three |
Pseudo-classes — state-based
a:hover { … } /* mouse is over it */
a:focus { … } /* keyboard focus */
a:focus-visible { … } /* focus, but only when keyboard-triggered */
a:active { … } /* being clicked */
a:visited { … } /* already visited (limited properties) */
input:disabled { … }
input:checked { … }
input:required { … }
input:valid { … }
input:invalid { … }
li:first-child { … }
li:last-child { … }
li:nth-child(2n) { … } /* every 2nd: 2, 4, 6… */
li:nth-child(odd){ … }
:not(.special) { … } /* negation */
:is(h1, h2, h3) { … } /* matches any of these */
:where(h1, h2) { … } /* like :is but zero specificity */
:has(img) { … } /* parent selector — has a descendant */
:root { … } /* the <html> element */
Pseudo-elements — generated content and parts
p::first-line { … }
p::first-letter { … }
p::selection { … } /* highlighted text */
.box::before {
content: "→ ";
}
.box::after {
content: "";
display: block;
clear: both;
}
input::placeholder { … }
li::marker { … }
:: vs :
Pseudo-classes use one colon (:hover) and select a state.
Pseudo-elements use two (::before) and select a generated
or sub-part of an element. Browsers still accept one colon for the old
pseudo-elements, but two is correct.
Specificity & Cascade
When two rules target the same element and property, the winner is decided by:
- Origin & importance —
!importantuser styles beat author styles beat browser defaults. - Specificity — how precisely the selector targets the element.
- Source order — later rules win ties.
Specificity score
Think of it as three numbers (A, B, C):
| Column | Counts |
|---|---|
| A | IDs (#header) |
| B | Classes, attributes, pseudo-classes (.card, [type], :hover) |
| C | Elements and pseudo-elements (p, ::before) |
p → (0,0,1)
.card → (0,1,0)
.card p → (0,1,1)
#header → (1,0,0)
#header .card p:hover → (1,2,1)
/* Inline styles beat everything except !important */
<p style="color: red"> → beats any selector
Keep specificity low
Use single classes everywhere. If you find yourself writing
#main .sidebar ul li a, you've built a trap that future-you
will fall into. .sidebar-link is better.
The !important escape hatch
.hidden { display: none !important; }
Useful for utility classes that must always win. Otherwise, it's a sign your specificity is out of control. Avoid it.
Inheritance
Some properties inherit from parent to child automatically — mostly text
properties: color, font-family,
font-size, line-height, text-align.
Box properties do not.
/* Force inheritance or reset it */
.child { color: inherit; }
.child { color: initial; } /* back to browser default */
.child { color: unset; } /* inherit if normally inherited, else initial */
The Box Model
Every element is a rectangle made of four layers. This one diagram fixes most beginner layout confusion.
┌─────────────── margin ────────────────┐
│ ┌───────────── border ─────────────┐ │
│ │ ┌────────── padding ─────────┐ │ │
│ │ │ │ │ │
│ │ │ content │ │ │
│ │ │ │ │ │
│ │ └────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
| Layer | What it is |
|---|---|
| content | The text or image itself. |
| padding | Space inside the border, between border and content. Background shows through. |
| border | The visible edge. |
| margin | Space outside the border, pushing neighbours away. Always transparent. |
.box {
width: 300px;
padding: 20px;
border: 5px solid black;
margin: 30px;
}
By default (box-sizing: content-box) the element above takes up
300 + 40 + 10 = 350px of horizontal space, plus margins. This is
almost never what you want.
/* Put this at the top of every stylesheet */
*, *::before, *::after {
box-sizing: border-box;
}
With border-box, the declared width includes padding
and border. The box is 300px wide, period.
Margin collapsing
Vertical margins between adjacent block elements collapse into the larger of the two, rather than adding. This surprises everyone once.
<p style="margin-bottom: 30px">A</p>
<p style="margin-top: 20px">B</p>
<!-- Gap between them is 30px, not 50px -->
Margins do not collapse when there's padding, a border, or a flex/grid container between them.
Shorthand
/* 1 value → all four sides */
padding: 10px;
/* 2 values → vertical | horizontal */
padding: 10px 20px;
/* 3 values → top | horizontal | bottom */
padding: 10px 20px 30px;
/* 4 values → top | right | bottom | left (clockwise) */
padding: 10px 20px 30px 40px;
/* Longhand */
padding-top: 10px;
margin-inline: auto; /* logical property: left+right in LTR */
margin-block: 1rem; /* logical property: top+bottom */
Display & Flow
| Value | Behaviour |
|---|---|
block | New line, full available width. Accepts width/height. |
inline | Flows with text. Width/height are ignored. |
inline-block | Flows with text but accepts width/height. |
none | Removed from layout entirely. Not the same as visibility: hidden. |
flex | Lays out children in one dimension. |
grid | Lays out children in two dimensions. |
contents | The element itself disappears; children take its place in the layout. |
display: none vs visibility: hidden vs opacity: 0
| Takes up space? | Read by screen readers? | Clickable? | |
|---|---|---|---|
display: none | No | No | No |
visibility: hidden | Yes | No | No |
opacity: 0 | Yes | Yes | Yes |
Overflow
.box {
overflow: hidden; /* clip anything that spills out */
overflow: auto; /* scrollbar only if needed */
overflow: scroll; /* scrollbar always */
overflow-x: auto; /* one axis only */
overflow-y: hidden;
}
Units & Colors
Length units
| Unit | Relative to | Best for |
|---|---|---|
px | Absolute pixels | Borders, hairlines, fixed sizes |
rem | The root font size (usually 16px) | Text, spacing, anything that should scale with user settings |
em | The element's own font size | Padding that scales with its own text |
% | The parent's size | Fluid widths |
vw / vh | 1% of viewport width / height | Full-screen sections |
vmin / vmax | 1% of the smaller / larger viewport dimension | Squares that fit either orientation |
ch | Width of the "0" character | Readable line lengths |
fr | A fraction of free space (Grid only) | Grid tracks |
Colors
color: red; /* named */
color: #ff0000; /* hex, 6 digits */
color: #f00; /* hex, 3-digit shorthand */
color: #ff000080; /* hex with alpha (50%) */
color: rgb(255, 0, 0); /* rgb */
color: rgba(255, 0, 0, 0.5); /* rgb with alpha */
color: hsl(0, 100%, 50%); /* hue, saturation, lightness */
color: hsl(0 100% 50% / 0.5); /* modern syntax with alpha */
color: transparent; /* fully transparent black */
color: currentColor; /* whatever the text color is */
/* Modern: define a colour once, adjust it */
:root { --brand: #0055ff; }
.box { color: color-mix(in srgb, var(--brand) 70%, white); }
HSL is easier to reason about
hsl(0, 100%, 50%) → red
hsl(120, 100%, 50%) → green
hsl(240, 100%, 50%) → blue
hsl(0, 100%, 25%) → dark red
hsl(0, 100%, 75%) → light red
hsl(0, 20%, 50%) → desaturated red (greyish)
Gradients
background: linear-gradient(to right, red, blue);
background: linear-gradient(135deg, #000 0%, #fff 100%);
background: radial-gradient(circle, red, blue);
background: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);
Typography
body {
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif;
font-size: 1rem; /* 16px by default */
line-height: 1.6; /* unitless: 1.6 × font-size */
font-weight: 400;
letter-spacing: 0;
color: #222;
}
h1 {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.02em;
}
code, pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
Font stacks
Always list several fonts, ending with a generic family. The browser uses the first one available.
/* System UI stack — fast, native-looking, no download */
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
/* Serif body text */
font-family: Georgia, "Times New Roman", serif;
/* Monospace */
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
Web fonts
@font-face {
font-family: "My Font";
src: url("my-font.woff2") format("woff2");
font-weight: 400;
font-display: swap; /* show fallback text immediately */
}
/* Or via a service */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");
Line length
Aim for 45–75 characters per line. Longer than that and the eye loses its
place returning to the next line. max-width: 65ch is a good
default for prose.
Other useful text properties
text-align: left | right | center | justify;
text-decoration: underline | line-through | none;
text-transform: uppercase | lowercase | capitalize;
text-indent: 2rem;
text-shadow: 1px 1px 2px rgba(0,0,0,.3);
word-break: break-word;
overflow-wrap: break-word; /* break long URLs */
white-space: nowrap | pre | pre-wrap;
text-overflow: ellipsis; /* needs overflow: hidden + nowrap */
font-variant-numeric: tabular-nums; /* aligned digits */
Flexbox
Flexbox lays out items along a single axis — a row or a column. It's the default tool for navigation bars, card rows, centering, and distributing space.
.container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
flex-wrap: nowrap; /* nowrap | wrap | wrap-reverse */
justify-content: flex-start;/* main axis alignment */
align-items: stretch; /* cross axis alignment */
align-content: normal; /* multi-line cross axis */
gap: 1rem; /* space between items */
}
justify-content — main axis
flex-start [■■■ ]
flex-end [ ■■■]
center [ ■■■ ]
space-between [■ ■ ■]
space-around [ ■ ■ ■ ■ ]
space-evenly [ ■ ■ ■ ]
align-items — cross axis
stretch items fill the container height
flex-start items sit at the top
flex-end items sit at the bottom
center items are vertically centred
baseline items align on their text baseline
Flex item properties
.item {
flex-grow: 1; /* how much spare space this item takes */
flex-shrink: 1; /* how much it shrinks when space is tight */
flex-basis: auto; /* its starting size */
/* Shorthand: grow shrink basis */
flex: 1; /* = 1 1 0 — equal widths */
flex: auto; /* = 1 1 auto — grow but respect content */
flex: none; /* = 0 0 auto — fixed size */
align-self: center; /* override the container's align-items */
order: 2; /* reorder visually (doesn't change DOM order) */
}
The classic centering trick
.center-everything {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
A navigation bar
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 2px solid black;
}
.navbar ul {
display: flex;
gap: 1.5rem;
list-style: none;
margin: 0;
padding: 0;
}
Flexbox gap
gap works in Flexbox in all modern browsers. It's far better than
the old negative-margin hacks.
Grid
Grid lays out items in two dimensions — rows and columns. Use it for page layouts, galleries, and anything where Flexbox would need nesting.
.container {
display: grid;
grid-template-columns: 200px 1fr 200px; /* sidebar | main | sidebar */
grid-template-rows: auto 1fr auto; /* header | body | footer */
gap: 1rem;
}
Defining tracks
/* Three equal columns */
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: repeat(3, 1fr);
/* Fixed + fluid */
grid-template-columns: 250px 1fr;
/* Responsive without a media query — auto-fit + minmax */
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
/* Named areas */
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
Placing items
.item {
grid-column: 1 / 3; /* from line 1 to line 3 (spans 2 columns) */
grid-column: span 2; /* same thing, relative */
grid-row: 2 / 4;
}
/* With named areas */
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Alignment
.container {
justify-items: stretch; /* align items within their cells (inline axis) */
align-items: stretch; /* (block axis) */
justify-content: start; /* align the whole track set */
align-content: start;
place-items: center; /* shorthand for both */
}
A complete page layout
.page {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 220px 1fr;
grid-template-rows: auto 1fr auto;
gap: 1rem;
min-height: 100vh;
}
@media (max-width: 700px) {
.page {
grid-template-areas:
"header"
"nav"
"main"
"footer";
grid-template-columns: 1fr;
}
}
Flexbox or Grid? Flexbox for one dimension (a row of buttons, a column of cards). Grid for two (a whole page layout). They work together — a Grid item can be a Flex container.
Positioning
| Value | Behaviour |
|---|---|
static | Default. Normal document flow. top/left/right/bottom are ignored. |
relative | Stays in flow, but can be nudged with offsets. Also creates a positioning context for children. |
absolute | Removed from flow. Positioned relative to the nearest positioned ancestor. |
fixed | Removed from flow. Positioned relative to the viewport. Stays put on scroll. |
sticky | Behaves like relative until it hits a threshold, then sticks. |
/* Positioning context + absolutely positioned child */
.card {
position: relative;
}
.badge {
position: absolute;
top: 0;
right: 0;
}
/* Full-screen overlay */
.overlay {
position: fixed;
inset: 0; /* shorthand for top/right/bottom/left: 0 */
background: rgba(0,0,0,.5);
}
/* Sticky header */
.header {
position: sticky;
top: 0;
z-index: 10;
background: white;
}
Stacking and z-index
z-index only works on positioned elements (or flex/grid items).
Higher numbers paint on top. Elements with the same z-index follow DOM order.
.modal { position: fixed; z-index: 1000; }
.tooltip { position: absolute; z-index: 1001; }
Avoid z-index wars
Keep a small, documented scale — 1, 10, 100, 1000. If you're writing
z-index: 99999, something has gone wrong earlier.
Responsive Design
Over half of all web traffic is mobile. Responsive design means one page that adapts to any screen.
<!-- Always include this -->
<meta name="viewport" content="width=device-width, initial-scale=1">
Mobile-first media queries
/* Base styles target small screens */
.container {
padding: 1rem;
font-size: 1rem;
}
/* Layer on as the screen grows */
@media (min-width: 600px) {
.container { padding: 2rem; }
}
@media (min-width: 900px) {
.container { max-width: 860px; margin: 0 auto; }
}
@media (min-width: 1200px) {
.container { max-width: 1100px; }
}
Mobile-first means you add complexity as space allows, rather than stripping it away. It produces simpler CSS and better performance on phones.
Other media features
@media (min-width: 600px) and (max-width: 899px) { … }
@media (orientation: landscape) { … }
@media (prefers-color-scheme: dark) { … }
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}
@media (hover: hover) { … } /* only devices with a real pointer */
@media print { … }
Fluid values with clamp()
/* clamp(minimum, preferred, maximum) */
h1 {
font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}
.section {
padding: clamp(1rem, 5vw, 4rem);
}
Responsive images
img {
max-width: 100%;
height: auto;
display: block;
}
/* Modern aspect ratio box */
.thumb {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
Transitions & Animations
Transitions
.button {
background: black;
color: white;
transition: background-color 0.2s ease, transform 0.15s ease;
}
.button:hover {
background: #333;
transform: translateY(-2px);
}
/* Shorthand: property duration timing-function delay */
transition: all 0.3s ease-in-out 0.1s;
/* Timing functions */
ease | linear | ease-in | ease-out | ease-in-out
cubic-bezier(0.68, -0.55, 0.27, 1.55) /* custom */
Animations with @keyframes
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 0.5s ease-out both;
}
/* Full shorthand */
animation-name: fadeInUp;
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0s;
animation-iteration-count: infinite; /* or a number */
animation-direction: alternate;
animation-fill-mode: both; /* keep start/end state */
animation-play-state: running | paused;
Spinner
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 32px;
height: 32px;
border: 4px solid #ddd;
border-top-color: black;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
Transforms
transform: translate(10px, 20px);
transform: translateX(-50%);
transform: scale(1.2);
transform: rotate(45deg);
transform: skew(10deg, 5deg);
transform: translate(-50%, -50%) scale(1.1); /* chained */
transform-origin: center center;
transform-style: preserve-3d;
perspective: 800px;
Respect reduced motion Some people get motion sick from animations. Always provide an opt-out:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Custom Properties (CSS Variables)
Define a value once, reuse it everywhere, and change it at runtime from JavaScript or a media query.
:root {
--brand: #0055ff;
--text: #111111;
--bg: #ffffff;
--space: 1rem;
--radius: 8px;
--shadow: 0 2px 8px rgba(0,0,0,.1);
}
.card {
background: var(--bg);
color: var(--text);
padding: var(--space);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
/* Fallback if the variable is undefined */
.card { color: var(--text, black); }
Scoped variables
/* Override just inside this component */
.dark-section {
--bg: #111;
--text: #eee;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--bg: #000;
--text: #fff;
}
}
/* Toggle via a class */
[data-theme="dark"] {
--bg: #000;
--text: #fff;
}
Changing them from JavaScript
// Set on the root
document.documentElement.style.setProperty("--brand", "#ff0000");
// Read it back
const value = getComputedStyle(document.documentElement)
.getPropertyValue("--brand");
Variables vs preprocessor variables Sass variables are resolved at build time and can't change at runtime. CSS custom properties are live — they cascade, they inherit, and JS can read and write them.
Modern CSS Features
Logical properties
Write once, work in both left-to-right and right-to-left languages.
/* Instead of */ /* Use */
margin-left margin-inline-start
margin-right margin-inline-end
margin-top margin-block-start
margin-bottom margin-block-end
padding-left padding-inline-start
width inline-size
height block-size
border-left border-inline-start
:is(), :where(), and :has()
/* Group without repeating */
:is(h1, h2, h3) { line-height: 1.2; }
/* Same, but zero specificity — easy to override */
:where(h1, h2, h3) { margin-top: 2rem; }
/* Style a parent based on its children */
.card:has(img) { padding-top: 0; }
form:has(:invalid) { border-color: red; }
Container queries
Style based on the size of a container, not the viewport. Perfect for reusable components.
.card-wrapper {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card { display: flex; gap: 1rem; }
}
Nesting
.card {
padding: 1rem;
background: white;
& h2 {
margin-top: 0;
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,.1);
}
&.featured {
border: 2px solid black;
}
}
aspect-ratio, gap, and inset
.video { aspect-ratio: 16 / 9; }
.grid { gap: 1rem; }
.overlay{ position: fixed; inset: 0; }
scroll-behavior and scroll snapping
html { scroll-behavior: smooth; }
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel > * {
scroll-snap-align: start;
flex: 0 0 100%;
}
Part IV — JavaScript
JavaScript Basics
JavaScript is a real programming language that runs in the browser (and, via Node.js, on the server). It lets you react to clicks, fetch data, and change the page after it has loaded.
Adding JavaScript to a page
<!-- External — preferred -->
<script src="app.js" defer></script>
<!-- Inline, inside <script> -->
<script>
console.log("Hello");
</script>
Declaring variables
const name = "Ada"; // can't be reassigned. Use by default.
let age = 36; // can be reassigned. Use when you must.
var old = true; // legacy, function-scoped. Avoid.
// const prevents reassignment, not mutation:
const list = [1, 2];
list.push(3); // ✓ fine, the array changed
// list = [4, 5]; // ✗ TypeError
Output
console.log("Hello"); // browser console
console.error("Problem"); // red, with a stack trace
console.warn("Careful"); // yellow
console.table([{a:1},{a:2}]);
console.time("loop"); /* … */ console.timeEnd("loop");
alert("Hi"); // blocking popup — avoid
document.title = "New tab title";
Types & Coercion
The primitive types
| Type | Examples | typeof returns |
|---|---|---|
| String | "hello", 'hi', `tpl` | "string" |
| Number | 42, 3.14, -0, NaN, Infinity | "number" |
| BigInt | 9007199254740993n | "bigint" |
| Boolean | true, false | "boolean" |
| Undefined | undefined | "undefined" |
| Null | null | "object" (historical bug) |
| Symbol | Symbol("id") | "symbol" |
| Object | {}, [], function(){} | "object" / "function" |
undefined vs null
let x; // undefined — never assigned
function f() {} // f() returns undefined
const y = null; // null — deliberately empty
x == null // true (loose: matches both)
x === null // false
y === null // true
Truthy and falsy
These eight values are falsy. Everything else is truthy.
false
0
-0
0n (BigInt zero)
"" (empty string)
null
undefined
NaN
if ("0") { } // runs — non-empty string is truthy
if ([]) { } // runs — empty array is truthy
if ({}) { } // runs — empty object is truthy
// A safe "is this set?" check
if (value) { } // falsy check
if (value != null) { } // defined and not null
Type coercion
"5" + 3 // "53" (+ prefers string concatenation)
"5" - 3 // 2 (- forces numbers)
"5" * "2" // 10
1 + true // 2
1 + "1" // "11"
[] + [] // ""
[] + {} // "[object Object]"
0.1 + 0.2 // 0.30000000000000004 (floating point)
// Convert explicitly
Number("42") // 42
Number("abc") // NaN
parseInt("42px") // 42
parseFloat("3.14") // 3.14
String(42) // "42"
Boolean(0) // false
(42).toString() // "42"
Operators
Arithmetic
5 + 2 // 7
5 - 2 // 3
5 * 2 // 10
5 / 2 // 2.5
5 % 2 // 1 (remainder / modulo)
5 ** 2 // 25 (exponent)
let n = 5;
n += 2; // 7
n -= 2; // 5
n *= 2; // 10
n /= 2; // 5
n++; // increment by 1
n--; // decrement by 1
Comparison
== loose equality — coerces types. Avoid.
=== strict equality — no coercion. Use this.
!= loose inequality
!== strict inequality
> < >= <= comparisons
"1" == 1 // true
"1" === 1 // false
NaN === NaN // false (use Number.isNaN())
Object.is(a, b) // like === but NaN equals NaN, and -0 ≠ +0
Logical
a && b // b if a is truthy, otherwise a
a || b // a if a is truthy, otherwise b
!a // boolean negation
!!a // coerce to boolean
a ?? b // b only if a is null or undefined (nullish coalescing)
// Optional chaining — safe access
user?.address?.city // undefined instead of a TypeError
arr?.[0]
fn?.()
// Nullish assignment
x ??= 5; // assign if x is null/undefined
x ||= 5; // assign if x is falsy
x &&= 5; // assign if x is truthy
Ternary
const label = age >= 18 ? "Adult" : "Minor";
Spread and rest
// Spread: expand an iterable
const a = [1, 2, 3];
const b = [...a, 4, 5]; // [1,2,3,4,5]
const o = { ...{x:1}, y: 2 }; // {x:1, y:2}
// Rest: collect the remainder
function sum(...nums) {
return nums.reduce((t, n) => t + n, 0);
}
const [first, ...others] = [1, 2, 3];
const { id, ...rest } = { id: 1, a: 2, b: 3 };
Control Flow
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else {
grade = "C";
}
switch (day) {
case "sat":
case "sun":
console.log("Weekend");
break;
default:
console.log("Weekday");
}
// Guard clause — return early instead of nesting
function process(user) {
if (!user) return;
if (!user.active) return;
// …the real work
}
Functions
// Declaration — hoisted, so callable before its definition
function greet(name) {
return `Hello, ${name}!`;
}
// Expression — not hoisted
const greet2 = function (name) {
return `Hello, ${name}!`;
};
// Arrow — no own `this`, concise
const greet3 = (name) => `Hello, ${name}!`;
// Arrow with a block body
const greet4 = (name) => {
const upper = name.toUpperCase();
return `Hello, ${upper}!`;
};
// Default parameters
function greet5(name = "stranger") {
return `Hello, ${name}!`;
}
// Rest parameters
function total(...nums) {
return nums.reduce((sum, n) => sum + n, 0);
}
Return values
function noReturn() { } // returns undefined
function early(x) {
if (x < 0) return; // bare return → undefined
return x * 2;
}
// Return multiple values via an array or object
function minMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [lo, hi] = minMax([3, 1, 4, 1, 5]);
Closures
function makeCounter() {
let count = 0; // private to the returned function
return function () {
count += 1;
return count;
};
}
const next = makeCounter();
next(); // 1
next(); // 2
next(); // 3
Callbacks and higher-order functions
function repeat(n, action) {
for (let i = 0; i < n; i++) action(i);
}
repeat(3, (i) => console.log("tick", i));
// Functions are values
const operations = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
};
operations.add(2, 3); // 5
Arrays
const fruits = ["apple", "banana", "cherry"];
fruits[0] // "apple"
fruits.length // 3
fruits.at(-1) // "cherry" (last item)
Array.isArray(fruits) // true
Mutating methods
| Method | Does | Returns |
|---|---|---|
push(x) | Add to the end | New length |
pop() | Remove from the end | Removed item |
unshift(x) | Add to the start | New length |
shift() | Remove from the start | Removed item |
splice(i, n, …) | Remove/insert at index | Removed items |
reverse() | Reverse in place | The array |
sort(fn) | Sort in place | The array |
fill(v) | Fill with a value | The array |
Non-mutating methods
| Method | Does |
|---|---|
slice(a, b) | Copy a portion |
concat(arr) | Join arrays |
join(sep) | Array → string |
indexOf(x) / lastIndexOf(x) | Find an index, or −1 |
includes(x) | Boolean membership test |
find(fn) / findIndex(fn) | First match |
some(fn) / every(fn) | Any / all match |
flat() / flatMap(fn) | Flatten nested arrays |
toSorted() / toReversed() | Non-mutating sort/reverse |
The big three: map, filter, reduce
const nums = [1, 2, 3, 4, 5];
// map — transform each item, same length
const doubled = nums.map((n) => n * 2); // [2,4,6,8,10]
// filter — keep items that pass a test
const evens = nums.filter((n) => n % 2 === 0); // [2,4]
// reduce — boil down to one value
const sum = nums.reduce((total, n) => total + n, 0); // 15
// Chaining
const result = nums
.filter((n) => n % 2 === 1)
.map((n) => n * 10)
.reduce((a, b) => a + b, 0); // 10 + 30 + 50 = 90
Destructuring and iteration
const [a, b, ...rest] = [1, 2, 3, 4];
// a=1, b=2, rest=[3,4]
for (const fruit of fruits) {
console.log(fruit);
}
fruits.forEach((fruit, i) => {
console.log(i, fruit);
});
Sorting correctly
// ✗ Wrong: sorts as strings
[10, 9, 1].sort(); // [1, 10, 9]
// ✓ Correct: numeric comparator
[10, 9, 1].sort((a, b) => a - b); // [1, 9, 10]
[10, 9, 1].sort((a, b) => b - a); // [10, 9, 1]
// Sort objects by a property
users.sort((a, b) => a.name.localeCompare(b.name));
Objects
const user = {
name: "Ada",
age: 36,
email: "ada@example.com",
greet() {
return `Hi, I'm ${this.name}`;
},
"favourite colour": "blue", // quoted key
};
// Access
user.name // dot notation
user["favourite colour"] // bracket notation, for odd keys
user.greet() // method call
// Add / change / delete
user.city = "London";
delete user.age;
// Check existence
"name" in user // true
user.hasOwnProperty("name") // true
Destructuring
const { name, age } = user;
const { name: userName, age: userAge = 0 } = user; // rename + default
// Nested
const { address: { city } = {} } = user;
// In function parameters
function display({ name, age }) {
return `${name}, ${age}`;
}
display(user);
Useful methods
Object.keys(user) // ["name", "age", …]
Object.values(user) // ["Ada", 36, …]
Object.entries(user) // [["name","Ada"], ["age",36], …]
Object.assign({}, a, b) // merge (mutates the first)
{ ...a, ...b } // merge (creates new)
Object.freeze(user) // make immutable
Object.fromEntries([["a",1],["b",2]]) // {a:1, b:2}
Iterating
for (const key in user) {
console.log(key, user[key]);
}
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
JSON
const text = JSON.stringify(user); // object → string
const obj = JSON.parse(text); // string → object
// Pretty-print
JSON.stringify(user, null, 2);
// JSON has no functions, undefined, or comments.
// Dates become strings.
Strings
const s = "Hello, world";
s.length // 12
s[0] // "H"
s.toUpperCase() // "HELLO, WORLD"
s.toLowerCase()
s.trim() // strip whitespace from both ends
s.includes("world") // true
s.startsWith("Hello") // true
s.endsWith("world") // true
s.indexOf("o") // 4 (or -1)
s.slice(0, 5) // "Hello"
s.substring(7) // "world"
s.replace("world", "there")
s.replaceAll("l", "L")
s.split(", ") // ["Hello", "world"]
s.repeat(2)
s.padStart(20, "-")
s.charAt(0)
s.charCodeAt(0)
// Template literals
const name = "Ada";
const msg = `Hello, ${name}! You have ${2 + 3} messages.`;
const multi = `line one
line two`;
Regular expressions
const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = "Date: 2026-10-12".match(re);
// match[0] = "2026-10-12", match[1] = "2026", …
/hello/i.test("Hello") // true (case-insensitive)
"a,b,c".split(/,/)
"a1b2c3".replace(/\d/g, "") // "abc"
" hi ".replace(/\s+/g, " ") // " hi "
// Named groups
const m = "2026-10-12".match(/(?<y>\d{4})-(?<m>\d{2})/);
m.groups.y; // "2026"
Loops & Iteration
// Classic for
for (let i = 0; i < 5; i++) {
console.log(i);
}
// for…of — values of an iterable
for (const item of ["a", "b", "c"]) {
console.log(item);
}
// for…in — keys of an object (avoid on arrays)
for (const key in { a: 1, b: 2 }) {
console.log(key);
}
// while
let n = 0;
while (n < 5) {
n++;
}
// do…while — runs at least once
do {
n--;
} while (n > 0);
// break and continue
for (const x of [1, 2, 3, 4]) {
if (x === 2) continue; // skip this iteration
if (x === 4) break; // exit the loop
console.log(x);
}
DOM Manipulation
The Document Object Model is the live tree of objects the browser builds from your HTML. JavaScript edits this tree; the browser repaints.
Finding elements
document.getElementById("header")
document.querySelector(".card") // first match
document.querySelectorAll(".card") // NodeList of all
document.getElementsByClassName("card") // live HTMLCollection
document.getElementsByTagName("p") // live HTMLCollection
// Scoped search
const card = document.querySelector(".card");
card.querySelector("a");
// Walking the tree
el.parentElement
el.children
el.firstElementChild
el.lastElementChild
el.nextElementSibling
el.previousElementSibling
Reading and changing content
el.textContent // all text, ignoring markup — safe
el.innerText // visible text only (forces layout)
el.innerHTML = "<b>Hi</b>"; // parses HTML — XSS risk with user input
el.setAttribute("data-id", "42");
el.getAttribute("data-id");
el.removeAttribute("data-id");
el.dataset.id // same as the data-id attribute
el.style.color = "red";
el.style.setProperty("--brand", "#f00");
el.classList.add("active");
el.classList.remove("active");
el.classList.toggle("active");
el.classList.contains("active");
el.classList.replace("a", "b");
Never use innerHTML with user input
It executes scripts. Use textContent for text, or
document.createElement() when you need structure.
Creating and inserting elements
const li = document.createElement("li");
li.textContent = "New item";
li.classList.add("item");
list.appendChild(li); // add at the end
list.prepend(li); // add at the start
list.append(li, "some text"); // append multiple things
list.before(li); // insert before the list
list.after(li); // insert after
li.remove(); // delete it
li.replaceWith(other); // swap it out
parent.innerHTML = ""; // nuke all children
Attributes vs properties
// Attributes are what's written in the HTML
input.getAttribute("value") // the original value
// Properties are the live state
input.value // what's currently typed
// For most things, use the property.
checkbox.checked = true;
input.value = "new text";
link.href = "https://example.com";
Events
const btn = document.querySelector("#my-button");
btn.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
});
// Named handler, so it can be removed
function handleClick(e) { console.log("clicked"); }
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);
The event object
element.addEventListener("click", (e) => {
e.target // the element that was clicked
e.currentTarget // the element the listener is attached to
e.type // "click"
e.preventDefault() // stop the default browser behaviour
e.stopPropagation() // stop the event bubbling up
e.clientX, e.clientY // mouse position
e.key // for keyboard events
});
Common events
| Category | Events |
|---|---|
| Mouse | click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave, contextmenu |
| Keyboard | keydown, keyup, keypress (deprecated) |
| Form | submit, reset, change, input, focus, blur, invalid |
| Document | DOMContentLoaded, load, resize, scroll, visibilitychange |
| Touch / Pointer | touchstart, touchend, pointerdown, pointerup, pointermove |
| Media | play, pause, ended, timeupdate |
| Drag | dragstart, dragover, drop |
Event delegation
Attach one listener to a parent instead of many to children. Works for dynamically added elements too.
document.querySelector("ul").addEventListener("click", (e) => {
const li = e.target.closest("li");
if (!li) return;
console.log("Clicked item:", li.textContent);
});
Waiting for the DOM
document.addEventListener("DOMContentLoaded", () => {
// safe to query elements here
});
// Or use defer on the script tag — simpler and preferred
// <script src="app.js" defer></script>
Keyboard and form example
const input = document.querySelector("#search");
input.addEventListener("input", (e) => {
console.log("Current value:", e.target.value);
});
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
runSearch(e.target.value);
}
});
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop the page reload
const data = new FormData(form);
console.log(Object.fromEntries(data));
});
Live demo
let clicks = 0;
const btn = document.querySelector("#demo-counter");
const out = document.querySelector("#demo-output");
btn.addEventListener("click", () => {
clicks += 1;
out.textContent = "Clicks: " + clicks;
});
Async & Promises
JavaScript runs on a single thread. Long operations — network requests, timers, file reads — are handed off and their results delivered later via callbacks.
Callbacks
setTimeout(() => {
console.log("2 seconds later");
}, 2000);
setInterval(() => {
console.log("every second");
}, 1000);
// Deep nesting is the "callback pyramid of doom" — avoid
Promises
const p = new Promise((resolve, reject) => {
const ok = true;
if (ok) resolve("Success!");
else reject(new Error("Failed"));
});
p.then((value) => console.log(value))
.catch((err) => console.error(err))
.finally(() => console.log("done"));
// Promise combinators
Promise.all([p1, p2, p3]) // all must succeed; rejects on first failure
Promise.allSettled([p1, p2]) // waits for all, never rejects
Promise.race([p1, p2]) // first to settle wins
Promise.any([p1, p2]) // first to succeed wins
async / await
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to load user:", error);
throw error; // re-throw if the caller should know
}
}
// An async function always returns a Promise
getUser(1).then((user) => console.log(user.name));
Running things in parallel
// ✗ Sequential — 3 round trips, one after another
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();
// ✓ Parallel — one round trip's worth of waiting
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
The event loop
JavaScript has one call stack and a task queue. Synchronous code runs to
completion; when the stack empties, the loop takes the next task from the
queue. That's why setTimeout(fn, 0) still runs after the current
code finishes.
Fetch & APIs
An API is a defined way for one program to talk to another. A web API exposes endpoints — URLs that return data, usually as JSON.
GET request
async function loadUsers() {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const users = await response.json();
return users;
}
loadUsers()
.then((users) => console.log(users))
.catch((err) => console.error(err));
POST request with a JSON body
async function createUser(data) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
Other response formats
await response.json() // parse JSON
await response.text() // plain text / HTML
await response.blob() // binary (images, files)
await response.formData() // multipart form data
await response.arrayBuffer() // raw bytes
response.status // 200
response.ok // true if 200–299
response.headers.get("content-type")
Timeouts and cancellation
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
} catch (err) {
if (err.name === "AbortError") console.log("Timed out");
} finally {
clearTimeout(timer);
}
Common HTTP methods (recap)
| Method | Means | CRUD |
|---|---|---|
GET | Read | R |
POST | Create | C |
PUT / PATCH | Replace / update | U |
DELETE | Remove | D |
Error Handling
try {
riskyOperation();
} catch (error) {
console.error(error.name); // "TypeError"
console.error(error.message); // human-readable
console.error(error.stack); // where it happened
} finally {
cleanup(); // always runs
}
Built-in error types
| Type | When |
|---|---|
TypeError | Wrong type — calling a non-function, reading a property of null |
ReferenceError | Using an undeclared variable |
SyntaxError | Invalid code — caught before running |
RangeError | Value out of range |
URIError | Malformed URI |
AggregateError | Multiple errors from Promise.any() |
Throwing your own
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
// Custom error class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
throw new ValidationError("email", "That doesn't look like an email");
Global handlers
window.addEventListener("error", (e) => {
console.error("Uncaught:", e.message);
});
window.addEventListener("unhandledrejection", (e) => {
console.error("Unhandled promise rejection:", e.reason);
});
Modern JavaScript (ES6+)
| Feature | Example |
|---|---|
let / const | Block-scoped variables |
| Arrow functions | (x) => x * 2 |
| Template literals | `Hello ${name}` |
| Destructuring | const { a, b } = obj; |
| Default parameters | function f(x = 1) {} |
| Rest / spread | ...args, [...arr] |
| Classes | class Foo extends Bar {} |
| Modules | import / export |
| Promises & async/await | await fetch(url) |
| Optional chaining | user?.address?.city |
| Nullish coalescing | value ?? "default" |
Array.prototype.flat | [[1],[2]].flat() |
| Object shorthand | { name, age } |
| Computed keys | { [key]: value } |
Map / Set | Keyed collections with better semantics than objects/arrays |
for…of | Iterate values instead of indices |
Map and Set
const m = new Map();
m.set("a", 1);
m.get("a"); // 1
m.has("a"); // true
m.size; // 1
const s = new Set([1, 2, 2, 3]);
s.size; // 3
s.has(2); // true
// Dedupe an array
const unique = [...new Set([1, 1, 2, 3, 3])]; // [1,2,3]
Modules
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export default function multiply(a, b) {
return a * b;
}
// app.js
import multiply, { PI, add } from "./math.js";
import * as math from "./math.js";
console.log(add(1, 2));
console.log(math.PI);
In the browser, load a module with type="module":
<script type="module" src="app.js"></script>
Modules are deferred by default They run after the HTML is parsed, and they always run in strict mode. Each module has its own scope — no globals leaking.
Classes
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return `${this.name} says ${this.sound}`;
}
get upperName() {
return this.name.toUpperCase();
}
static create(name) {
return new Animal(name, "…");
}
}
class Dog extends Animal {
constructor(name) {
super(name, "woof");
this.legs = 4;
}
speak() {
return super.speak() + "!"; // call the parent version
}
}
const d = new Dog("Rex");
d.speak(); // "Rex says woof!"
d instanceof Animal // true
Classes are sugar
Under the hood they're prototype-based. The this keyword refers to
the instance — except in arrow functions, which inherit it from the enclosing
scope. That's why methods used as callbacks often need
.bind(this) or an arrow wrapper.
Part V — The Browser
How Browsers Render
- Parse HTML → build the DOM tree.
- Parse CSS → build the CSSOM tree.
- Combine them into a render tree (visible elements only).
- Layout (reflow) — compute the position and size of every box.
- Paint — fill in pixels.
- Composite — combine layers, often on the GPU.
Steps 4–6 are expensive. Changing a width triggers layout, paint,
and composite. Changing opacity or transform can skip
straight to composite — which is why animations of those properties are fast.
Blocking resources
- CSS blocks rendering. Nothing paints until the stylesheet loads.
- JavaScript blocks parsing. The HTML parser stops when it hits a plain
<script>. asyncdownloads in parallel and runs as soon as it's ready (order not guaranteed).deferdownloads in parallel and runs after parsing, in order.
<script src="a.js"></script> <!-- blocks parsing -->
<script src="a.js" async></script> <!-- parallel, order not kept -->
<script src="a.js" defer></script> <!-- parallel, order kept, runs last -->
Reflow vs repaint
| Change | Triggers |
|---|---|
width, height, font-size, margin | Layout + paint + composite (slow) |
color, background, visibility | Paint + composite |
transform, opacity | Composite only (fast) |
DevTools
Open with F12, Ctrl+Shift+I, or right-click → Inspect.
| Panel | Use it for |
|---|---|
| Elements | Inspect the DOM, edit HTML and CSS live, see the box model and computed styles. |
| Console | Run JavaScript, read errors and logs. |
| Sources | Set breakpoints, step through code, inspect variables. |
| Network | See every request: status, size, timing, headers, response body. Throttle to slow 3G. |
| Performance | Record a session and find what's blocking the main thread. |
| Application | Inspect cookies, localStorage, sessionStorage, IndexedDB, service workers. |
| Lighthouse | Audit performance, accessibility, SEO, and best practices. |
| Device Toolbar | Simulate screen sizes and mobile devices (Ctrl+Shift+M). |
Debugging with breakpoints
// In Sources, click a line number to set a breakpoint.
// Or write it directly in code:
debugger;
// Console API
console.log(...)
console.table(arr)
console.dir(obj) // inspect as a JS object
console.group("Label") // collapsible group
console.groupEnd()
console.count("calls")
console.trace() // print the call stack
Useful console tricks
// $ = querySelector, $$ = querySelectorAll (in Chrome/Firefox)
$(".card");
$$(".card");
// Inspect the last value
$_
// Copy an object to the clipboard
copy(someObject)
Storage
| Mechanism | Size | Lifetime | Sent to server? |
|---|---|---|---|
| Cookies | ~4 KB each | Set expiry; can be session-only | Yes, with every request |
| localStorage | ~5–10 MB | Until cleared | No |
| sessionStorage | ~5–10 MB | Until the tab closes | No |
| IndexedDB | Hundreds of MB | Until cleared | No |
| Cache API | Large | Until cleared | No |
localStorage
// Strings only — serialize objects yourself
localStorage.setItem("theme", "dark");
localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear();
// Storing an object
localStorage.setItem("user", JSON.stringify({ id: 1, name: "Ada" }));
const user = JSON.parse(localStorage.getItem("user"));
Don't store secrets localStorage is readable by any script on the page, including one injected via an XSS flaw. Never put tokens you can't afford to leak there.
Cookies
// Set from JavaScript (rarely the right approach)
document.cookie = "name=Ada; max-age=86400; path=/; SameSite=Lax";
// Read
document.cookie; // "name=Ada; other=value"
Cookie attributes:
| Attribute | Meaning |
|---|---|
Secure | Only sent over HTTPS |
HttpOnly | Not readable by JavaScript (protects against XSS) |
SameSite=Strict|Lax|None | Controls whether it's sent on cross-site requests (protects against CSRF) |
Max-Age / Expires | When it's deleted |
Path / Domain | Which URLs it applies to |
Performance
Core Web Vitals
| Metric | Measures | Target |
|---|---|---|
| LCP — Largest Contentful Paint | When the main content appears | < 2.5 s |
| INP — Interaction to Next Paint | Responsiveness to input | < 200 ms |
| CLS — Cumulative Layout Shift | How much the page jumps around | < 0.1 |
Practical wins
- Compress and resize images. Serve WebP or AVIF.
- Add
widthandheightto every<img>to prevent layout shift. - Lazy-load offscreen images:
<img loading="lazy">. - Minify and compress text assets (gzip/Brotli).
- Defer non-critical JavaScript.
- Cache aggressively with
Cache-Controlheaders. - Animate only
transformandopacity. - Avoid layout thrashing — batch reads then writes.
// ✗ Layout thrashing: read, write, read, write…
for (const el of els) {
el.style.height = el.offsetHeight + 10 + "px";
}
// ✓ Batch: read everything, then write everything
const heights = els.map((el) => el.offsetHeight);
els.forEach((el, i) => { el.style.height = heights[i] + 10 + "px"; });
Measuring
console.time("work");
doExpensiveThing();
console.timeEnd("work");
performance.now(); // high-resolution timestamp
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
}
}).observe({ type: "largest-contentful-paint", buffered: true });
Part VI — Tooling & Workflow
Editors
VS Code is the default choice. Other options: WebStorm (paid, powerful), Sublime Text (fast, minimal), Neovim (terminal-based, steep curve).
VS Code essentials
| Shortcut | Does |
|---|---|
Ctrl+P | Quick-open a file |
Ctrl+Shift+P | Command palette |
Ctrl+/ | Toggle comment |
Alt+↑/↓ | Move line |
Shift+Alt+↓ | Duplicate line |
Ctrl+D | Select next occurrence |
Ctrl+Shift+F | Search across files |
F2 | Rename symbol |
Ctrl+` | Toggle integrated terminal |
Extensions worth installing
- Live Server — instant-reload dev server for static files.
- Prettier — automatic formatting on save.
- ESLint — flags problems as you type.
- Error Lens — shows errors inline.
- Path Intellisense — autocompletes file paths.
- GitLens — see who changed each line and when.
- indent-rainbow — colour-codes indentation.
Git & GitHub
Git tracks changes to your files. GitHub hosts Git repositories online.
First-time setup
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
The daily workflow
git status # what's changed?
git diff # what exactly changed?
git add . # stage everything
git add file.js # stage one file
git commit -m "Add login form" # save a snapshot
git log --oneline # see history
git push # upload to GitHub
Working with branches
git branch # list branches
git checkout -b feature/login # create and switch
git switch main # modern: switch branches
git merge feature/login # merge into current branch
git branch -d feature/login # delete a merged branch
Undoing things
git restore file.js # discard unstaged changes
git restore --staged file.js # unstage, keep changes
git commit --amend # fix the last commit message
git reset --soft HEAD~1 # undo last commit, keep changes staged
git reset --hard HEAD~1 # undo last commit, DESTROY changes
git revert <commit> # safe undo: creates a new commit
Remotes
git remote add origin https://github.com/user/repo.git
git remote -v
git clone https://github.com/user/repo.git
git pull # fetch + merge
git fetch # download without merging
Merge conflicts
<<<<<<< HEAD
your version
=======
their version
>>>>>>> other-branch
Edit the file to keep what you want, delete the markers, then
git add and git commit.
A good .gitignore
node_modules/
dist/
build/
.env
.DS_Store
*.log
.vscode/
Never commit secrets
API keys, tokens, and .env files. Once pushed, they're in the
history forever — rotate the key immediately if it happens.
Node & npm
Node.js lets you run JavaScript outside the browser. npm installs JavaScript packages.
node --version
npm --version
npm init -y # create package.json
npm install express # install a dependency
npm install --save-dev jest # install a dev dependency
npm install -g some-cli # install globally
npm uninstall express
npm update
npm run <script-name>
package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"vitest": "^1.0.0"
}
}
Version ranges
| Range | Meaning |
|---|---|
1.2.3 | Exactly 1.2.3 |
^1.2.3 | Any 1.x.x ≥ 1.2.3 |
~1.2.3 | Any 1.2.x ≥ 1.2.3 |
>=1.2.3 | Anything at least 1.2.3 |
* | Anything |
package-lock.json pins the exact versions actually installed.
Commit it. node_modules/ — never commit it.
Build Tools
| Tool | Role |
|---|---|
| Bundler | Combines many files into a few. Webpack, Rollup, esbuild, Vite. |
| Transpiler | Converts modern syntax to older syntax. Babel, SWC, esbuild. |
| Dev server | Serves your app locally with hot reload. Vite, webpack-dev-server. |
| Task runner | Automates repeated jobs. npm scripts, Gulp. |
Vite — the modern default
npm create vite@latest my-app
cd my-app
npm install
npm run dev # dev server with instant hot reload
npm run build # production build → dist/
npm run preview # serve the production build locally
Vite uses native ES modules during development, so there's almost no startup delay. For production it bundles with Rollup.
You don't need a build tool to start Write plain HTML, CSS, and JS files and open them in a browser. Add tooling when the friction of not having it exceeds the friction of learning it.
Linters & Formatters
A linter finds likely bugs and bad patterns. A formatter rewrites code to a consistent style so you never argue about it.
Prettier
npm install --save-dev prettier
npx prettier --write .
npx prettier --check .
.prettierrc
{
"semi": true,
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"trailingComma": "es5"
}
ESLint
npm install --save-dev eslint
npx eslint --init
npx eslint .
eslint.config.js
export default [
{
rules: {
"no-unused-vars": "warn",
"no-undef": "error",
"eqeqeq": ["error", "always"],
"prefer-const": "warn",
},
},
];
Testing
| Type | Tests | Tools |
|---|---|---|
| Unit | One function or module in isolation | Vitest, Jest |
| Integration | Several units working together | Vitest, Jest, Testing Library |
| End-to-end | A real browser clicking through real flows | Playwright, Cypress |
| Visual regression | Screenshots compared against a baseline | Playwright, Percy |
A Vitest example
// math.js
export function add(a, b) { return a + b; }
// math.test.js
import { describe, it, expect } from "vitest";
import { add } from "./math.js";
describe("add", () => {
it("adds two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("handles negatives", () => {
expect(add(-1, -1)).toBe(-2);
});
});
A Playwright example
import { test, expect } from "@playwright/test";
test("homepage shows the title", async ({ page }) => {
await page.goto("http://localhost:5173");
await expect(page.locator("h1")).toHaveText("Hello, world!");
});
The testing pyramid
╱╲ few, slow, expensive
╱E2E╲
╱─────╲
╱Integr.╲ some
╱─────────╲
╱ Unit ╲ many, fast, cheap
╱─────────────╲
Deployment
| Host | Best for | Notes |
|---|---|---|
| GitHub Pages | Static sites | Free, tied to your repo |
| Netlify | Static + serverless functions | Generous free tier, drag-and-drop deploys |
| Vercel | Static + serverless, Next.js | Excellent DX |
| Cloudflare Pages | Static + edge functions | Very fast global network |
| Render / Railway / Fly.io | Full servers and databases | Good free/cheap tiers |
| AWS / GCP / Azure | Everything | Powerful, complex, easy to overspend |
Deploying to GitHub Pages
# 1. Push your repo to GitHub
git add .
git commit -m "Ready to deploy"
git push
# 2. On GitHub: Settings → Pages → Source: main branch / root
# 3. Your site appears at https://username.github.io/repo-name/
Environment variables
# .env (never commit this)
API_KEY=abc123
DATABASE_URL=postgres://…
# Load in Node
process.env.API_KEY
# Vite exposes only variables prefixed with VITE_
# (so you don't accidentally leak secrets to the client)
import.meta.env.VITE_PUBLIC_URL
Anything shipped to the browser is public Environment variables in a front-end build are compiled into the bundle. They are not secret. Only the server can keep secrets.
Part VII — Backend Basics
Servers & Node
A server listens for HTTP requests and sends responses. It can serve static files, run business logic, query a database, and render HTML.
A minimal Node server
server.jsimport http from "node:http";
const server = http.createServer((req, res) => {
if (req.url === "/api/hello") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello from the server" }));
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not found");
}
});
server.listen(3000, () => {
console.log("Listening on http://localhost:3000");
});
The same thing with Express
import express from "express";
const app = express();
app.use(express.json()); // parse JSON request bodies
app.get("/api/hello", (req, res) => {
res.json({ message: "Hello from the server" });
});
app.post("/api/users", (req, res) => {
const { name } = req.body;
if (!name) return res.status(400).json({ error: "Name required" });
res.status(201).json({ id: 1, name });
});
app.listen(3000, () => console.log("http://localhost:3000"));
Middleware
Middleware functions run in order between the request and your route handler. They can read the request, modify it, end it, or pass it on.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // pass control onward
});
// Auth middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "Unauthorized" });
req.user = verifyToken(token);
next();
}
app.get("/api/profile", requireAuth, (req, res) => {
res.json(req.user);
});
REST APIs
REST is a convention for structuring an API around resources and HTTP methods.
| Method + path | Means |
|---|---|
GET /posts | List all posts |
GET /posts/42 | Get post 42 |
POST /posts | Create a post |
PUT /posts/42 | Replace post 42 |
PATCH /posts/42 | Update part of post 42 |
DELETE /posts/42 | Delete post 42 |
Conventions
- Use nouns for paths, not verbs:
/posts, not/getPosts. - Pluralise collections:
/users,/users/42. - Use HTTP status codes properly.
- Return JSON with a consistent shape.
- Version the API:
/api/v1/users. - Paginate large collections:
?page=2&limit=20.
GraphQL in one paragraph
An alternative to REST where the client specifies exactly what fields it wants in a single request. One endpoint, a typed schema, no over-fetching. More powerful, more setup.
Databases
| SQL (relational) | NoSQL (document) | |
|---|---|---|
| Structure | Tables, rows, columns | Collections of JSON-like documents |
| Schema | Strict, defined up front | Flexible |
| Relationships | Foreign keys and JOINs | Embedding or references |
| Query language | SQL | Varies (MongoDB uses its own) |
| Examples | PostgreSQL, MySQL, SQLite | MongoDB, Firestore, DynamoDB |
| Good for | Structured data, complex relationships, transactions | Rapid iteration, varied shapes, horizontal scale |
SQL essentials
-- Create
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created TIMESTAMP DEFAULT NOW()
);
-- Insert
INSERT INTO users (name, email) VALUES ('Ada', 'ada@example.com');
-- Read
SELECT id, name FROM users WHERE email LIKE '%@example.com' ORDER BY name LIMIT 10;
-- Update
UPDATE users SET name = 'Ada Lovelace' WHERE id = 1;
-- Delete
DELETE FROM users WHERE id = 1;
-- Join
SELECT posts.title, users.name
FROM posts
JOIN users ON posts.user_id = users.id
WHERE users.id = 1;
Always parameterise queries String-concatenating user input into SQL is how SQL injection happens. Use placeholders.
// ✗ dangerous
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// ✓ safe
db.query("SELECT * FROM users WHERE email = $1", [email]);
Authentication
| Approach | How it works |
|---|---|
| Session cookies | Server stores a session; browser holds a cookie ID. Server-rendered apps. |
| JWT | Server signs a token containing claims; client sends it in the Authorization header. Stateless. |
| OAuth 2.0 | "Sign in with Google." You delegate auth to a third party. |
| Magic link | Email a one-time login link. No password. |
| Passkeys / WebAuthn | Biometric or hardware-key login. Phishing-resistant. |
Password storage — the only rule
// NEVER store plain passwords. Hash with a slow algorithm.
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 12);
const ok = await bcrypt.compare(attempt, hash);
JWT in practice
// Server: issue
const token = jwt.sign({ userId: 42 }, SECRET, { expiresIn: "1h" });
// Client: send
fetch("/api/profile", {
headers: { Authorization: `Bearer ${token}` }
});
// Server: verify
const payload = jwt.verify(token, SECRET);
Security checklist
- Hash passwords with bcrypt, scrypt, or Argon2. Never MD5 or SHA-1.
- Serve everything over HTTPS.
- Set
HttpOnly,Secure,SameSiteon cookies. - Validate and sanitise all input on the server. Client validation is UX, not security.
- Parameterise every database query.
- Escape output to prevent XSS.
- Use CSRF tokens for cookie-based state changes.
- Rate-limit login and password-reset endpoints.
- Keep dependencies updated —
npm audit. - Set a Content-Security-Policy header.
Part VIII — Putting It Together
Your First Project — A Working Counter
Three files in one folder. No tools, no build step. Open
index.html in a browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Counter</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="card">
<h1>Counter</h1>
<p id="count" aria-live="polite">0</p>
<div class="buttons">
<button id="decrement">−1</button>
<button id="reset">Reset</button>
<button id="increment">+1</button>
</div>
</main>
<script src="app.js" defer></script>
</body>
</html>
styles.css
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: system-ui, sans-serif;
background: #fafafa;
}
.card {
border: 2px solid #000;
background: #fff;
padding: 2rem 2.5rem;
text-align: center;
min-width: 280px;
}
h1 {
margin: 0 0 .5rem;
font-size: 1.1rem;
letter-spacing: .1em;
text-transform: uppercase;
}
#count {
font-size: 3.5rem;
margin: .5rem 0 1.5rem;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.buttons {
display: flex;
gap: .5rem;
justify-content: center;
}
button {
font: inherit;
font-weight: 600;
padding: .55rem 1rem;
border: 2px solid #000;
background: #fff;
cursor: pointer;
transition: background-color .15s, color .15s;
}
button:hover {
background: #000;
color: #fff;
}
button:focus-visible {
outline: 3px solid #000;
outline-offset: 2px;
}
app.js
let count = 0;
const display = document.querySelector("#count");
const increment = document.querySelector("#increment");
const decrement = document.querySelector("#decrement");
const reset = document.querySelector("#reset");
function render() {
display.textContent = count;
display.style.color = count < 0 ? "#b00" : "#000";
}
increment.addEventListener("click", () => { count += 1; render(); });
decrement.addEventListener("click", () => { count -= 1; render(); });
reset.addEventListener("click", () => { count = 0; render(); });
// Bonus: keyboard shortcuts
document.addEventListener("keydown", (e) => {
if (e.key === "+" || e.key === "ArrowUp") { count += 1; render(); }
if (e.key === "-" || e.key === "ArrowDown") { count -= 1; render(); }
if (e.key === "0") { count = 0; render(); }
});
render();
What you just used
- Semantic HTML with a
<main>landmark aria-liveso screen readers announce the count changing- CSS Grid for centering, Flexbox for the button row
box-sizing: border-box, custom hover and focus states- JavaScript closures, event listeners, DOM manipulation
deferso the script runs after the DOM is ready
Common Errors Decoded
| Message | What it means | Fix |
|---|---|---|
Cannot read properties of null (reading 'x') |
Your selector matched nothing. | Check spelling and case; make sure the script runs after the element exists. |
X is not defined |
You used a variable that doesn't exist. | Typo, or a missing const/let. |
X is not a function |
You called something that isn't callable. | Typo in a method name, or the script loaded twice. |
Cannot set properties of undefined |
Chained access past a missing level. | Use optional chaining a?.b?.c or guard with an if. |
Uncaught (in promise) … |
An async operation failed unhandled. | Add try/catch around await. |
Blocked by CORS policy |
The other origin didn't allow your site. | Fix it server-side with an Access-Control-Allow-Origin header — or proxy through your own backend. |
404 Not Found |
The path is wrong. | Check the file location and case. Servers are case-sensitive. |
Mixed Content |
An HTTPS page tried to load an HTTP resource. | Change the URL to https://. |
Maximum call stack size exceeded |
Infinite recursion. | Find the function calling itself with no exit condition. |
| CSS not applying | Selector mismatch, missing link, or override. | Check the Network tab, then check specificity in the Styles panel. |
| Layout jumps after load | Images without dimensions. | Add width and height attributes to every image. |
| Everything is 0px tall | Floated or absolutely positioned children don't contribute height. | Use Flexbox or Grid instead, or add a clearfix. |
Debugging routine
- Read the error message properly. It usually names the file and line.
- Open DevTools → Console. Fix the first error, not the tenth.
console.log()the value you're unsure about.- Set a breakpoint and step through.
- Change one thing at a time.
- If stuck for 30 minutes, explain the problem out loud. You'll often solve it.
Glossary A — Acronyms & Abbreviations
| Term | Stands for | In plain English |
|---|---|---|
| a11y | accessibility | Numeronym: a + 11 letters + y. Making sites usable by everyone. |
| AJAX | Asynchronous JavaScript and XML | Loading data in the background without a full page reload. |
| API | Application Programming Interface | A contract for how one program asks another for things. |
| ARIA | Accessible Rich Internet Applications | HTML attributes that describe UI to screen readers. |
| AST | Abstract Syntax Tree | A tree representation of code, used by linters and compilers. |
| BEM | Block, Element, Modifier | A CSS class naming convention: .card__title--large. |
| CDN | Content Delivery Network | A global server network that serves files from wherever is closest. |
| CI/CD | Continuous Integration / Continuous Deployment | Automatically testing and shipping code when you push. |
| CLI | Command Line Interface | Controlling a program by typing commands in a terminal. |
| CMS | Content Management System | Editing site content without touching code (WordPress, etc.). |
| CORS | Cross-Origin Resource Sharing | The browser rule blocking a page from calling a different domain unless allowed. |
| CRUD | Create, Read, Update, Delete | The four basic operations on data. |
| CSRF | Cross-Site Request Forgery | An attack that tricks a logged-in user's browser into making a request. |
| CSS | Cascading Style Sheets | The language that controls how HTML looks. |
| CSSOM | CSS Object Model | The parsed, in-memory representation of your stylesheets. |
| CSR | Client-Side Rendering | The browser builds the page with JavaScript. |
| CTA | Call To Action | The button you want the user to press. |
| DOM | Document Object Model | The live tree of objects the browser builds from your HTML. |
| DNS | Domain Name System | The internet's phone book: names to IP addresses. |
| DX | Developer Experience | How pleasant a tool is to work with. |
| ES6 | ECMAScript 6 (2015) | The big JavaScript update that added classes, arrows, promises, modules. |
| FOUC | Flash Of Unstyled Content | The brief moment a page shows unstyled HTML before CSS loads. |
| FTP | File Transfer Protocol | An older way to move files to a server. |
| GUI | Graphical User Interface | Point-and-click interface, as opposed to a CLI. |
| HTML | HyperText Markup Language | The language that gives a page its structure. |
| HTTP / HTTPS | HyperText Transfer Protocol (Secure) | The rules browsers and servers use. The S means encrypted. |
| i18n | internationalization | Numeronym. Making software work in many languages. |
| IDE | Integrated Development Environment | A code editor with a debugger, terminal, and more built in. |
| IIFE | Immediately Invoked Function Expression | (function(){ … })() — runs the moment it's defined. |
| INP | Interaction to Next Paint | A Core Web Vital measuring responsiveness. |
| IP | Internet Protocol | Addressing and routing packets between machines. |
| JSON | JavaScript Object Notation | A text format for structured data; the lingua franca of APIs. |
| JS / JSX | JavaScript / JavaScript XML | JSX lets you write HTML-like markup inside JavaScript (React). |
| JWT | JSON Web Token | A signed token used to prove who a user is. |
| k8s | Kubernetes | Numeronym. Container orchestration. |
| LAMP | Linux, Apache, MySQL, PHP | A classic all-in-one back-end stack. |
| LCP | Largest Contentful Paint | A Core Web Vital measuring when main content appears. |
| MIME | Multipurpose Internet Mail Extensions | The type label for content: text/html, application/json. |
| MVC | Model–View–Controller | A pattern organising code into data, display, and logic. |
| npm | Node Package Manager | The tool and registry for installing JavaScript libraries. |
| ORM | Object–Relational Mapper | Code that translates between objects and database tables. |
| PWA | Progressive Web App | A website that can be installed and work offline like a native app. |
| REST | Representational State Transfer | An API style built around URLs and HTTP methods. |
| SEO | Search Engine Optimization | Making a site easier for search engines to find and rank. |
| SPA | Single Page Application | One HTML page where JavaScript swaps content instead of reloading. |
| SQL | Structured Query Language | The language for querying relational databases. |
| SSG / SSR | Static Site Generation / Server-Side Rendering | Building HTML ahead of time / building it per request on the server. |
| SSL / TLS | Secure Sockets Layer / Transport Layer Security | The encryption behind HTTPS. TLS is the modern version. |
| TCP | Transmission Control Protocol | Reliable, ordered delivery of packets. |
| UI / UX | User Interface / User Experience | What it looks like / how it feels to use. |
| URI / URL | Uniform Resource Identifier / Locator | The address of a resource on the web. |
| UUID | Universally Unique Identifier | A long random ID like f47ac10b-58cc-…. |
| VCS | Version Control System | Software that tracks file changes over time. Git is one. |
| VDOM | Virtual DOM | An in-memory copy of the DOM that frameworks diff against. Used by React. |
| WASM | WebAssembly | A fast binary format letting other languages run in the browser. |
| WCAG | Web Content Accessibility Guidelines | The standard for making sites usable by people with disabilities. |
| XSS | Cross-Site Scripting | Injecting malicious script into a page through user input. |
| XML | eXtensible Markup Language | A general markup format; largely replaced by JSON for APIs. |
Numeronyms — words shortened with numbers
Developers compress long words by keeping the first and last letter and putting the count of the missing letters in between.
- a11y — accessibility
- i18n — internationalization
- l10n — localization
- k8s — Kubernetes
- o11y — observability
- p13n — personalization
- c14n — canonicalization
- s12n — serialization
Glossary B — Jargon & Idioms
Core concepts
- Boilerplate
- Standard code you copy into every project before real work begins. "Start from the boilerplate."
- Vanilla JS
- Plain JavaScript with no frameworks or libraries. "I built it in vanilla JS."
- Syntactic sugar
- Syntax that makes code nicer to read without adding new capability. Arrow functions and template literals are sugar.
- Polyfill
- Code that adds a modern feature to older browsers that don't support it.
- Shim
- Similar to a polyfill, but intercepts and corrects existing behaviour rather than adding a missing one.
- Transpiler
- A tool that converts one language version into another — TypeScript into JavaScript, or modern JS into older JS.
- Bundler
- A tool that combines many files into a few for the browser. Webpack, Rollup, esbuild, Vite.
- Linter
- A tool that reads your code and flags likely mistakes and style violations. ESLint is the standard for JavaScript.
- Minify
- Strip whitespace and shorten names to shrink file size. Makes code unreadable to humans, which is fine for production.
- Uglify
- An older minifier. The verb "uglify" means the same as minify.
- Tree shaking
- Removing code from your bundle that nothing actually imports.
- Code splitting
- Splitting a bundle into chunks that load on demand, so the first page is smaller.
- Hot reload
- The dev server updates the page the instant you save a file, without losing state.
- HMR
- Hot Module Replacement — hot reload at the module level.
- Scaffolding
- Generating a project's folder structure and starter files automatically.
- Closure
- A function that remembers the variables from where it was defined, even after that scope has finished running.
- Hoisting
- JavaScript's behaviour of moving declarations to the top of their scope before running the code.
- Scope
- Where a variable is visible.
constandletare block-scoped;varis function-scoped. - Lexical scope
- Scope determined by where code is written, not where it's called.
- Callback
- A function you pass to another function to be run later.
- Callback hell
- Deeply nested callbacks that are hard to read. Solved by promises and async/await.
- Promise / async-await
- Two ways to handle operations that take time without freezing the page.
- Event listener
- Code that waits for something to happen — a click, a keypress, a scroll — then runs.
- Event bubbling
- An event fires on the target, then on each ancestor in turn, up to the document.
- Event capturing
- The opposite phase: the event travels down from the document to the target first.
- Debounce
- Delay running a function until the user stops doing something. Used for search-as-you-type.
- Throttle
- Run a function at most once every N milliseconds. Used for scroll handlers.
- Breakpoint
- The screen width at which a media query kicks in and your layout changes.
- Viewport
- The visible area of the page in the browser window.
- Semantic HTML
- Using tags that describe their content (
<nav>,<article>) instead of generic<div>s. - Progressive enhancement
- Build a working baseline first, then layer on enhancements for capable browsers.
- Graceful degradation
- The opposite: build the full experience, then make sure it degrades acceptably on older browsers.
- Mobile-first
- Write base styles for small screens, then add complexity with
min-widthmedia queries. - FOUC
- Flash Of Unstyled Content — the ugly moment before CSS loads.
- Hydration
- Attaching JavaScript behaviour to server-rendered HTML in the browser.
- Idempotent
- Doing it twice has the same result as doing it once. GET and DELETE should be idempotent.
- Pure function
- A function with no side effects: same input, same output, every time.
- Side effect
- Anything a function does beyond returning a value — writing to the DOM, making a request, logging.
- Immutable
- Cannot be changed after creation. React code often treats data as immutable.
- Memoization
- Caching the result of an expensive function so repeat calls are instant.
- Recursion
- A function that calls itself. Always needs a base case.
- Higher-order function
- A function that takes a function as an argument or returns one.
map,filter,reduceare all examples.
Workflow & team slang
- Repo
- Short for repository — a project folder tracked by Git.
- Commit
- A saved snapshot of your changes with a message explaining them.
- Branch
- A parallel copy of the code where you can work without affecting
main. - Fork
- Your own copy of someone else's repository, usually on GitHub.
- Clone
- Downloading a repository to your machine.
- PR / Pull Request
- A request to merge your branch into the main one, usually with review and discussion.
- MR
- Merge Request — GitLab's name for a pull request.
- Diff
- A line-by-line comparison of two versions of a file.
- Merge conflict
- Git can't automatically combine two sets of changes to the same lines. You resolve it by hand.
- Rebase
- Replaying your commits on top of another branch, producing a linear history.
- Cherry-pick
- Copying a single commit from one branch onto another.
- Stash
- Temporarily shelving uncommitted changes so you can switch tasks.
- HEAD
- The commit you're currently on.
- Origin
- The default name for the remote repository you cloned from.
- Upstream
- The original repository a fork came from.
- Issue
- A tracked task, bug report, or feature request in a project.
- Backlog
- The list of work not yet started.
- Sprint
- A fixed time period (usually 1–2 weeks) in which a set of work is completed.
- Standup
- A short daily meeting where each person says what they did, what they'll do, and what's blocking them.
- Ship it
- Deploy it to real users. "Looks good — ship it."
- Prod / staging / dev
- The live environment / a realistic copy for testing / your local machine.
- Deploy
- Publishing your code so it's reachable on the internet.
- Rollback
- Reverting a deployment to the previous version.
- Hotfix
- An urgent fix pushed straight to production.
- Feature flag
- A switch that turns a feature on or off without deploying new code.
- Canary release
- Rolling a change out to a small percentage of users first.
- Environment variable
- A value stored outside your code (like an API key) so it isn't committed to Git.
- Seed data
- Fake or initial data loaded into a database for development.
- Dogfooding
- Using your own product internally to find problems before users do.
- Rubber duck debugging
- Explaining your bug out loud to an inanimate object. You usually find the answer mid-sentence.
- Yak shaving
- Being pulled into a chain of unrelated tasks before you can do the thing you set out to do.
- Bikeshedding
- Spending disproportionate time arguing about something trivial while the hard problems go unaddressed.
- Spaghetti code
- Tangled, hard-to-follow code with no clear structure.
- Lasagna code
- The opposite problem: so many layers of abstraction that nothing is traceable.
- Copy-pasta
- Code copied from somewhere else without understanding it.
- Code smell
- A pattern that isn't a bug but suggests deeper problems.
- Technical debt
- Shortcuts taken now that will cost more effort to fix later.
- Refactoring
- Improving the structure of code without changing what it does.
- Magic number
- A hard-coded value with no explanation. "Why 47? Name it."
- Magic string
- The same, but for text.
- Hard-coded
- Baked directly into the code rather than configurable.
- DRY
- Don't Repeat Yourself — factor out duplication.
- WET
- Write Everything Twice — the anti-pattern DRY warns against.
- KISS
- Keep It Simple, Stupid — the simplest solution is usually right.
- YAGNI
- You Aren't Gonna Need It — don't build for imagined future requirements.
- SOLID
- Five principles of object-oriented design. Useful to know the name of.
- foo / bar / baz
- Conventional placeholder names used in examples when the actual name doesn't matter.
- Lorem ipsum
- Filler Latin text used to preview layouts before real copy exists.
- Lorem ipsum dolor
- The opening words of the standard filler passage.
- Sandbox
- An isolated environment where it's safe to experiment or run untrusted code.
- Cache
- A stored copy of something so it can be reused faster. "Have you tried clearing your cache?" is the oldest joke in the industry.
- Cache busting
- Adding a hash to a filename (
app.a3f9c.js) so browsers fetch the new version. - CDN edge
- A server in a CDN's network close to the user.
- Endpoint
- A specific URL an API exposes. GET /users/1
- Payload
- The actual data in a request or response, as opposed to the headers.
- Query string
- The part of a URL after the
?, carrying parameters. - Rate limit
- A cap on how many requests you can make in a time window.
- Throttling (API)
- Deliberately slowing or limiting requests.
- Webhook
- A reverse API call: the server notifies your app when something happens.
- Polling
- Repeatedly asking the server for updates on a timer.
- WebSocket
- A persistent two-way connection, for chat and live updates.
- SSE
- Server-Sent Events — one-way streaming from server to browser.
Quick Cheat Sheet
HTML boilerplate
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main></main>
<script src="app.js" defer></script>
</body>
</html>
CSS reset starter
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
body {
min-height: 100vh;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
Centering, all the ways
/* Flexbox */
.parent { display: flex; justify-content: center; align-items: center; }
/* Grid */
.parent { display: grid; place-items: center; }
/* Absolutely positioned */
.child {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* Horizontal centering of a block */
.child { margin-inline: auto; width: fit-content; }
JavaScript DOM snippets
// Select
const el = document.querySelector(".card");
// Create
const li = document.createElement("li");
li.textContent = "New";
document.querySelector("ul").append(li);
// Event
el.addEventListener("click", (e) => { … });
// Toggle a class
el.classList.toggle("active");
// Store
localStorage.setItem("key", JSON.stringify(data));
const data = JSON.parse(localStorage.getItem("key"));
// Fetch
const res = await fetch("/api/items");
const items = await res.json();
Git daily commands
git status
git add .
git commit -m "message"
git push
git pull
git checkout -b new-branch
git log --oneline
Terminal basics
pwd # where am I
ls -la # list all files
cd folder # go into folder
cd .. # go up
mkdir name # make a folder
rm file # delete a file (rm -rf folder for folders)
cp a b # copy
mv a b # move / rename
cat file # print a file
touch file # create an empty file
Learning Path
Phase 1 — The basics (2–4 weeks)
- HTML: structure, semantic elements, forms, accessibility.
- CSS: selectors, box model, Flexbox, Grid, responsive design.
- JavaScript: variables, functions, arrays, objects, DOM, events.
- Build: a personal page, a to-do list, a calculator, a quiz.
Phase 2 — Working with data (2–4 weeks)
- Async JavaScript, fetch, promises, error handling.
- Git and GitHub properly: branches, PRs, conflicts.
- DevTools in depth: Network, Sources, Application panels.
- Build: a weather app, a movie search, a GitHub profile viewer.
Phase 3 — Tools and structure (2–4 weeks)
- npm, package.json, Vite.
- Modules, ES6+, classes.
- Linting and formatting.
- Deployment to Netlify or GitHub Pages.
- Build: a multi-page site, a markdown blog, a small game.
Phase 4 — Pick a direction
- Front-end path: React or Vue, state management, component design, accessibility in depth.
- Back-end path: Node + Express, databases, auth, REST design, deployment.
- Full-stack path: both, plus an ORM and a hosting platform.
Phase 5 — Go deeper
- Testing: unit, integration, end-to-end.
- Performance: Core Web Vitals, profiling, optimisation.
- TypeScript.
- System design basics: caching, queues, scaling, observability.
The one rule that matters Build things. Tutorials feel productive but the learning happens when you're stuck on your own project with no instructions and you have to figure it out. Pick something small and slightly too hard.
Trusted Resources
| Resource | What it is |
|---|---|
MDN Web Docsdeveloper.mozilla.org | The definitive reference for HTML, CSS, and JavaScript. If you read one site, read this one. |
| web.dev | Google's guides on performance, accessibility, and best practice. |
Can I Usecaniuse.com | Browser support tables for every web feature. |
| JavaScript.info | A thorough, modern JavaScript tutorial. |
| The Odin Project | A free, structured full-stack curriculum. |
| freeCodeCamp | Free interactive courses and certifications. |
| CSS-Tricks | Deep dives on CSS, especially Flexbox and Grid. |
| Frontend Mentor | Real design files to build from — great for practice. |
| Smashing Magazine | Long-form articles on front-end and design. |
| Stack Overflow | Q&A. Search before asking; read the accepted answer's comments. |
| GitHub | Read other people's code. It's the best teacher there is. |
W3C Validatorvalidator.w3.org | Checks your HTML for errors. |
A note on tutorials Watching someone build something feels like progress. It isn't. Type the code yourself, then close the tutorial and rebuild it from memory. Then change something the tutorial didn't cover. That last step is where the learning actually happens.