URLs & HTTP
URL Anatomy
A URL (Uniform Resource Locator) is the address of a resource on the web. Every part has a purpose.
https://api.example.com:8443/v2/users?role=admin&active=true#results
│ │ │ │ │ │
protocol domain port path query string fragment
Protocol (Scheme)
https:// or http://. The protocol tells the browser how to communicate with the server. HTTPS encrypts the connection with TLS. In practice, everything should be HTTPS.
Other schemes exist: ftp://, mailto:, tel:, data:, blob:. The browser handles each differently.
Domain (Host)
api.example.com -- the human-readable name that DNS resolves to an IP address. Subdomains (api.) are part of the domain and can point to different servers.
Port
:8443 -- the network port on the server. Defaults are 80 for HTTP and 443 for HTTPS. If you use the default, the port is omitted from the URL. Non-default ports are common in development (:3000, :8080).
Path
/v2/users -- identifies a specific resource on the server. Paths are hierarchical and look like file system paths, though they do not have to map to actual files.
Query String
?role=admin&active=true -- key-value pairs that provide additional parameters. Starts with ?, pairs separated by &. Values must be URL-encoded (spaces become %20 or +).
// Building query strings safely in JavaScript
const params = new URLSearchParams({
role: 'admin',
search: 'jane doe',
active: 'true'
});
console.log(params.toString());
role=admin&search=jane+doe&active=true
Fragment
#results -- identifies a section within the page. The fragment is never sent to the server. It is purely client-side. Single-page applications use fragments (or the History API) for client-side routing.
Parsing URLs in JavaScript
The URL API provides reliable URL parsing:
const url = new URL('https://shop.example.com:8443/products?category=books&page=2#top');
console.log(url.protocol); // "https:"
console.log(url.hostname); // "shop.example.com"
console.log(url.port); // "8443"
console.log(url.pathname); // "/products"
console.log(url.search); // "?category=books&page=2"
console.log(url.hash); // "#top"
// Access individual query parameters
console.log(url.searchParams.get('category')); // "books"
console.log(url.searchParams.get('page')); // "2"
Never parse URLs with string splitting or regex. The URL constructor handles edge cases you will miss.
HTTP/1.1 vs HTTP/2 vs HTTP/3
HTTP/1.1 (1997)
The workhorse of the web for two decades. Key characteristics:
- Text-based protocol -- headers are plain text
- One request per connection at a time (head-of-line blocking)
- Browsers work around this by opening 6 parallel connections per domain
Connection: keep-alivereuses connections for multiple requests
Connection 1: GET /style.css ──────────────> response ──> GET /app.js ──> response
Connection 2: GET /hero.jpg ──────────────> response ──> GET /logo.png ──> response
HTTP/2 (2015)
A major performance upgrade. Key improvements:
- Binary framing -- more efficient to parse than text
- Multiplexing -- multiple requests and responses on a single connection simultaneously
- Header compression (HPACK) -- headers are compressed, removing redundancy
- Server push -- the server can proactively send resources (rarely used in practice)
Single connection:
Stream 1: GET /style.css ──> response
Stream 2: GET /app.js ───> response (all in parallel)
Stream 3: GET /hero.jpg ──> response
HTTP/2 made many HTTP/1.1 performance hacks unnecessary. Concatenating files, sprite sheets, and domain sharding can actually hurt performance with HTTP/2.
HTTP/3 (2022)
Built on QUIC instead of TCP. Key improvements:
- No head-of-line blocking at the transport layer -- a lost packet on one stream does not block others
- Faster connection setup -- QUIC combines the TCP and TLS handshakes into one round trip
- Connection migration -- switching from Wi-Fi to cellular does not break the connection
- Uses UDP as the underlying transport
HTTP/3 adoption is growing. Most major browsers and CDNs support it.
Request Headers in Detail
Headers modify the request behavior. These are the ones you will encounter daily.
Content Negotiation
Accept: application/json # I want JSON back
Accept: text/html, application/xhtml+xml # I prefer HTML
Accept-Language: en-US, en;q=0.9, fr;q=0.8 # Language preference with quality
Accept-Encoding: gzip, deflate, br # I can handle compressed responses
The q value (quality) indicates preference. q=1.0 is highest (default), q=0 means "do not send this."
Authentication
Authorization: Bearer eyJhbGciOiJIUzI1NiIs... # JWT token
Authorization: Basic dXNlcjpwYXNz # Base64-encoded user:pass
Bearer tokens are the standard for APIs. Basic auth is rarely used except for simple internal tools.
Content Description
Content-Type: application/json # Body is JSON
Content-Type: multipart/form-data # Body contains file uploads
Content-Type: application/x-www-form-urlencoded # Body is form data
Content-Length: 348 # Body size in bytes
Content-Type tells the server how to parse the request body. Getting this wrong is a common source of 400 errors.
Response Headers in Detail
Caching
Cache-Control: max-age=3600 # Cache for 1 hour
Cache-Control: no-cache # Always revalidate with server
Cache-Control: no-store # Never cache (sensitive data)
Cache-Control: public, max-age=31536000 # Cache for 1 year (immutable assets)
ETag: "abc123" # Fingerprint for revalidation
Last-Modified: Wed, 18 Apr 2026 12:00:00 GMT
no-cache does not mean "don't cache." It means "cache it, but check with the server before using it." no-store means "do not cache at all."
Cookies
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
| Attribute | Purpose |
|---|---|
HttpOnly |
JavaScript cannot read this cookie (prevents XSS theft) |
Secure |
Only sent over HTTPS |
SameSite=Lax |
Not sent with cross-site requests (CSRF protection) |
SameSite=Strict |
Not sent with any cross-site navigation |
Max-Age |
Seconds until expiration (or Expires for a specific date) |
Path |
Which paths this cookie applies to |
Domain |
Which domains this cookie applies to |
Security Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
These headers protect against common attacks. Strict-Transport-Security forces HTTPS. Content-Security-Policy controls which resources the page can load.
Cookies, Sessions & Statelessness
HTTP is stateless -- each request is independent. The server does not inherently remember who you are between requests. Cookies solve this.
How Sessions Work
1. Client: POST /login (username + password)
2. Server: Validates credentials
Creates session: { id: "abc123", user_id: 42 }
Stores session in memory/database
Responds: Set-Cookie: session_id=abc123
3. Client: GET /dashboard
Cookie: session_id=abc123
4. Server: Looks up session "abc123"
Finds user_id: 42
Returns dashboard for that user
Cookies vs Tokens
Session cookies store a session ID. The actual session data lives on the server.
JWT (JSON Web Tokens) encode the session data in the token itself. The server does not need to store session state.
Session cookie: session_id=abc123 → Server looks up "abc123"
JWT: eyJhbGciOi...eyJ1c2VyIjo... → Server decodes the token directly
Trade-offs:
- Cookies are simpler and can be revoked instantly (delete the session from the server)
- JWTs are stateless (no server-side storage) but cannot be revoked until they expire
- Cookies work naturally with browsers; JWTs need JavaScript to manage
URL Encoding
Special characters in URLs must be encoded. Spaces, ampersands, and equals signs all have special meaning in URLs.
// Encode a single component
encodeURIComponent('hello world & goodbye');
// "hello%20world%20%26%20goodbye"
// Encode a full URI (preserves :, /, ?, #, etc.)
encodeURI('https://example.com/search?q=hello world');
// "https://example.com/search?q=hello%20world"
Use encodeURIComponent for query parameter values. Use encodeURI for full URLs (it preserves structural characters). Better yet, use the URLSearchParams API and let it handle encoding.
Common Pitfalls
- Not URL-encoding query parameters:
?search=rock & rollbreaks because&is a delimiter. Always encode user input. - Confusing
no-cacheandno-store:no-cachestill caches;no-storedoes not. For sensitive data (banking, health records), useno-store. - Missing
HttpOnlyon session cookies: Without it, any XSS vulnerability can steal session cookies viadocument.cookie. - Building URLs with string concatenation: Use the
URLandURLSearchParamsAPIs. String concatenation leads to encoding bugs and injection vulnerabilities. - Ignoring HTTP/2 when optimizing: Concatenating CSS/JS files, using sprite sheets, or domain sharding can hurt HTTP/2 performance. Check what protocol your server uses before optimizing.
- Setting
Cache-Controlwrong on HTML: Cache static assets aggressively (with hashed filenames), but serve HTML withno-cacheor shortmax-ageso users always get the latest version.
Key Takeaways
- URLs have a precise structure: protocol, domain, port, path, query string, fragment
- Fragments are never sent to the server -- they are client-side only
- HTTP/2 multiplexes requests on a single connection; HTTP/3 adds QUIC for even better performance
- Headers are not boilerplate -- they control caching, security, authentication, and content negotiation
- HTTP is stateless; cookies and tokens are the mechanisms for maintaining session state
Cache-Controlis the most impactful header for performance -- understandmax-age,no-cache, andno-store- Always use the
URLandURLSearchParamsAPIs instead of manual string manipulation - Security headers (
HttpOnly,Secure,SameSite, CSP, HSTS) are not optional for production