5 min read
On this page

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-alive reuses 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 & roll breaks because & is a delimiter. Always encode user input.
  • Confusing no-cache and no-store: no-cache still caches; no-store does not. For sensitive data (banking, health records), use no-store.
  • Missing HttpOnly on session cookies: Without it, any XSS vulnerability can steal session cookies via document.cookie.
  • Building URLs with string concatenation: Use the URL and URLSearchParams APIs. 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-Control wrong on HTML: Cache static assets aggressively (with hashed filenames), but serve HTML with no-cache or short max-age so 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-Control is the most impactful header for performance -- understand max-age, no-cache, and no-store
  • Always use the URL and URLSearchParams APIs instead of manual string manipulation
  • Security headers (HttpOnly, Secure, SameSite, CSP, HSTS) are not optional for production