· 13 min read

HTTP Fundamentals for Senior Frontend Developers

Master HTTP concepts, methods, status codes, headers, and performance optimizations that every senior frontend developer should know for technical interviews.

As a senior frontend developer, understanding HTTP is crucial for building performant, secure, and scalable web applications. This comprehensive guide covers everything you need to know for technical interviews and real-world development.

What is HTTP?

HTTP (HyperText Transfer Protocol) is the foundation of data communication on the web. It’s an application-layer protocol that defines how clients and servers communicate over the internet.

Key Characteristics:

  • Stateless: Each request is independent
  • Request-Response: Client sends request, server responds
  • Text-based: Human-readable format
  • Connectionless: No persistent connection between requests

HTTP Methods (Verbs)

Core Methods

GET

  • Retrieves data from server
  • Safe and repeatable (same result every time)
  • Data in URL parameters
  • Cacheable by default

Usage: Most common method for fetching data from APIs, loading web pages, and retrieving resources. Used for read-only operations that don’t modify server state.

// Fetch user data
const response = await fetch('/api/users/123');
const user = await response.json();

// Fetch with query parameters
const response = await fetch('/api/posts?category=tech&limit=10');
const posts = await response.json();

POST

  • Submits data to server
  • Not safe or repeatable (can have different results)
  • Data in request body
  • Not cacheable by default

Usage: Used for creating new resources, submitting forms, file uploads, and any operation that modifies server state. Each request can have different effects, making it non-repeatable.

// Create a new user
const response = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' }),
});

// Submit form data
const formData = new FormData();
formData.append('title', 'My Post');
formData.append('content', 'Post content here');
const response = await fetch('/api/posts', { method: 'POST', body: formData });

PUT

  • Creates or updates entire resource
  • Repeatable (same result every time) but not safe
  • Replaces entire resource

Usage: Used for complete resource updates or creation when you know the exact resource identifier. The entire resource is replaced with the provided data. Safe to retry as it produces the same result.

// Update entire user profile
const response = await fetch('/api/users/123', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 123, name: 'John Smith', email: 'johnsmith@example.com', role: 'admin' }),
});

// Create or replace a specific resource
const response = await fetch('/api/settings/theme', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ theme: 'dark', fontSize: 'large' }),
});

PATCH

  • Partial updates to resource
  • Not safe or repeatable (can have different results)
  • Modifies specific fields

Usage: Used for partial updates when you only want to modify specific fields of a resource. More efficient than PUT for small changes. Not safe to retry as it can have different effects depending on current state.

// Update only user's email
const response = await fetch('/api/users/123', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'newemail@example.com' }),
});

// Update multiple specific fields
const response = await fetch('/api/posts/456', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Updated Title', published: true }),
});

DELETE

  • Removes resource
  • Repeatable (same result every time) but not safe
  • May return 204 (No Content)

Usage: Used to remove resources from the server. Safe to retry as deleting an already deleted resource has the same effect. Often returns 204 No Content on success.

// Delete a user
const response = await fetch('/api/users/123', { method: 'DELETE' });

// Delete with confirmation
const response = await fetch('/api/posts/456', { method: 'DELETE', headers: { Authorization: 'Bearer ' + token } });

if (response.status === 204) {
  console.log('Resource deleted successfully');
}

Additional Methods

HEAD

  • Like GET but returns only headers
  • Useful for checking resource existence
  • No response body

Usage: Used to check if a resource exists, get metadata, or verify last modification time without downloading the actual content. Saves bandwidth when you only need header information.

// Check if a file exists
const response = await fetch('/api/files/document.pdf', { method: 'HEAD' });

if (response.ok) {
  const lastModified = response.headers.get('Last-Modified');
  const contentLength = response.headers.get('Content-Length');
  console.log(`File exists, last modified: ${lastModified}, size: ${contentLength}`);
}

// Check resource availability
const response = await fetch('/api/status', { method: 'HEAD' });
const isAvailable = response.status === 200;

OPTIONS

  • Returns allowed methods for resource
  • Used for CORS preflight requests

Usage: Used for CORS preflight requests when making cross-origin requests with certain methods or headers. Also used to discover what operations are allowed on a resource. Browsers automatically send this before certain requests.

// CORS preflight (browser sends this automatically)
const response = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer token' },
  body: JSON.stringify({ name: 'John' }),
});

// Check allowed methods for a resource
const response = await fetch('/api/users/123', { method: 'OPTIONS' });
const allowedMethods = response.headers.get('Allow');
console.log('Allowed methods:', allowedMethods); // "GET, PUT, DELETE"

HTTP Status Codes

1xx Informational

  • 100 Continue: Client should continue request
  • 101 Switching Protocols: Upgrading to different protocol

2xx Success

  • 200 OK: Request successful
  • 201 Created: Resource created successfully
  • 202 Accepted: Request accepted but not processed
  • 204 No Content: Success but no content returned
  • 206 Partial Content: Partial content returned (useful for range requests)

3xx Redirection

  • 301 Moved Permanently: Resource permanently moved
  • 302 Found: Resource temporarily moved
  • 304 Not Modified: Resource not changed (caching)
  • 307 Temporary Redirect: Temporary redirect preserving method
  • 308 Permanent Redirect: Permanent redirect preserving method

4xx Client Error

  • 400 Bad Request: Malformed request
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource doesn’t exist
  • 405 Method Not Allowed: HTTP method not supported
  • 408 Request Timeout: Request took too long
  • 409 Conflict: Resource conflict
  • 413 Payload Too Large: Request too large
  • 429 Too Many Requests: Rate limiting

5xx Server Error

  • 500 Internal Server Error: Generic server error
  • 501 Not Implemented: Method not implemented
  • 502 Bad Gateway: Invalid response from upstream
  • 503 Service Unavailable: Server temporarily unavailable
  • 504 Gateway Timeout: Upstream server timeout

HTTP Headers

HTTP headers are key-value pairs that provide additional information about the request or response. They contain metadata that helps clients and servers understand how to process the communication. Headers are case-insensitive and can be used for authentication, content negotiation, caching, security, and more.

Request Headers

Authentication

Authorization: Bearer <token>
Authorization: Basic <base64-encoded-credentials>

Content Negotiation

Content negotiation allows the client to tell the server what format, language, or encoding it prefers for the response. The server then responds with the best match it can provide.

Accept: application/json, text/html
Accept-Language: en-US, en
Accept-Encoding: gzip, deflate, br

Caching

Request caching headers tell the server about cached resources the client already has, allowing the server to decide if it should send the full response or just a “not modified” status.

Cache-Control: no-cache, max-age=3600
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
If-None-Match: "abc123"

CORS

Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

Response Headers

Caching

Response caching headers tell the client how long to cache the resource and provide validation tokens to check if the resource has changed.

Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

CORS

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

Security

Content-Security-Policy: default-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000

HTTP/2 vs HTTP/1.1

HTTP/1.1 Limitations

  • One request per connection (without pipelining)
  • Headers sent with every request
  • No server push capability
  • Head-of-line blocking

HTTP/2 Improvements

  • Multiplexing: Multiple requests over single connection
  • Header Compression: HPACK algorithm reduces overhead
  • Server Push: Server can send resources proactively
  • Binary Protocol: More efficient than text-based
  • Stream Prioritization: Important resources load first

Multiplexing

Allows multiple requests and responses to be sent simultaneously over a single TCP connection, eliminating the need for multiple connections.

HTTP/1.1 Problem:

Connection 1: GET /api/users     → Response 1
Connection 2: GET /api/posts     → Response 2
Connection 3: GET /api/comments  → Response 3

HTTP/2 Solution:

Single Connection:
Stream 1: GET /api/users     → Response 1
Stream 2: GET /api/posts     → Response 2
Stream 3: GET /api/comments  → Response 3

Header Compression (HPACK)

Reduces header size by using a compression table that stores previously sent headers.

Example:

First Request:
GET /api/users HTTP/2
Host: api.example.com
Authorization: Bearer abc123
Content-Type: application/json

Second Request (compressed):
GET /api/posts HTTP/2
:authority: api.example.com  // Compressed Host
:authorization: Bearer abc123  // Compressed Authorization

Server Push

Server can send resources to the client before they’re requested, reducing round trips.

Example:

// Client requests HTML
GET /index.html HTTP/2

// Server responds with HTML AND pushes CSS/JS
HTTP/2 200 OK
// Pushes: styles.css, app.js, logo.png

Binary Protocol

Uses binary frames instead of text, making parsing faster and more efficient.

HTTP/1.1 (Text):

GET /api/users HTTP/1.1\r\n
Host: api.example.com\r\n
Authorization: Bearer token\r\n
\r\n

HTTP/2 (Binary Frames):

[Frame Header][Frame Payload]
[Type: HEADERS][Length: 50][Stream: 1]
[Compressed Headers Data]

Stream Prioritization

Allows setting priority levels for different resources, ensuring critical content loads first.

Example:

// High priority: Critical CSS
GET /critical.css HTTP/2
Priority: weight=256, exclusive=true

// Medium priority: Images
GET /hero-image.jpg HTTP/2
Priority: weight=128, depends=1

// Low priority: Analytics
GET /analytics.js HTTP/2
Priority: weight=32, depends=1

Performance Optimization

Caching Strategies

Browser Caching

// Cache-Control headers
Cache-Control: public, max-age=31536000  // 1 year
Cache-Control: private, max-age=3600     // 1 hour
Cache-Control: no-cache                  // Always validate
Cache-Control: no-store                  // Never cache

ETags and Conditional Requests

// Server sends ETag
ETag: "abc123"

// Client sends conditional request
If-None-Match: "abc123"
// Server responds with 304 if unchanged

Compression

  • Gzip: Most common, good compression ratio
  • Brotli: Better compression than gzip
  • Deflate: Less common, similar to gzip

Connection Optimization

  • Keep-Alive: Reuse connections
  • HTTP/2: Multiplexing reduces connection overhead
  • CDN: Geographic distribution of resources

HTTP/3

HTTP/3 is the latest version of HTTP, built on top of QUIC protocol instead of TCP. It provides significant improvements over HTTP/2.

Key Features

QUIC Protocol

  • Built on UDP instead of TCP
  • Faster connection establishment
  • Better performance over unreliable networks
  • Built-in encryption

Improved Performance

  • Faster Handshakes: 0-RTT connection resumption
  • Better Multiplexing: No head-of-line blocking
  • Connection Migration: Seamless network switching
  • Improved Congestion Control: Better handling of packet loss

Example:

// HTTP/3 is automatically used when supported
fetch('https://example.com/api/data')
  .then((response) => response.json())
  .then((data) => console.log(data));

// Check if HTTP/3 is supported
if ('connection' in navigator) {
  const connection = navigator.connection;
  console.log('Protocol:', connection.effectiveType);
}

WebSockets

WebSockets provide full-duplex communication between client and server over a single TCP connection, enabling real-time data exchange.

Key Characteristics

Persistent Connection

  • Single TCP connection
  • Low latency communication
  • Real-time bidirectional data flow
  • No HTTP overhead after handshake

Use Cases

  • Real-time chat applications
  • Live notifications
  • Collaborative editing
  • Live data feeds
  • Gaming applications

WebSocket API

Server-Side Implementation (Node.js with ws library):

const WebSocket = require('ws');
const http = require('http');

// Create HTTP server
const server = http.createServer();

// Create WebSocket server
const wss = new WebSocket.Server({ server });

// Handle WebSocket connections
wss.on('connection', function connection(ws, request) {
  console.log('New client connected');

  // Send welcome message
  ws.send(JSON.stringify({ type: 'welcome', message: 'Connected to WebSocket server', timestamp: Date.now() }));

  // Handle incoming messages
  ws.on('message', function incoming(data) {
    try {
      const message = JSON.parse(data);
      console.log('Received:', message);

      // Echo message back to client
      ws.send(JSON.stringify({ type: 'echo', originalMessage: message, timestamp: Date.now() }));

      // Broadcast to all connected clients
      wss.clients.forEach(function each(client) {
        if (client !== ws && client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify({ type: 'broadcast', message: message, timestamp: Date.now() }));
        }
      });
    } catch (error) {
      console.error('Error parsing message:', error);
      ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON format' }));
    }
  });

  // Handle client disconnect
  ws.on('close', function close() {
    console.log('Client disconnected');
  });

  // Handle errors
  ws.on('error', function error(err) {
    console.error('WebSocket error:', err);
  });
});

// Start server
server.listen(8080, function () {
  console.log('WebSocket server running on port 8080');
});

Client-Side Connection:

// Create WebSocket connection
const socket = new WebSocket('wss://api.example.com/ws');

// Connection opened
socket.onopen = function (event) {
  console.log('Connected to server');
  socket.send('Hello Server!');
};

// Message received
socket.onmessage = function (event) {
  console.log('Message from server:', event.data);
  const data = JSON.parse(event.data);
  // Handle real-time data
};

// Connection closed
socket.onclose = function (event) {
  console.log('Connection closed:', event.code, event.reason);
};

// Error occurred
socket.onerror = function (error) {
  console.error('WebSocket error:', error);
};

Sending Data:

// Send text message
socket.send('Hello Server!');

// Send JSON data
socket.send(JSON.stringify({ type: 'message', content: 'Hello World', timestamp: Date.now() }));

// Send binary data
const buffer = new ArrayBuffer(8);
socket.send(buffer);

Connection States:

// Check connection state
if (socket.readyState === WebSocket.OPEN) {
  socket.send('Data');
}

// States:
// WebSocket.CONNECTING = 0
// WebSocket.OPEN = 1
// WebSocket.CLOSING = 2
// WebSocket.CLOSED = 3

WebSocket vs HTTP

HTTP (Request-Response)

  • Stateless communication
  • Client initiates requests
  • Server responds to requests
  • Higher overhead per message

WebSocket (Bidirectional)

  • Persistent connection
  • Server can push data anytime
  • Lower overhead per message
  • Real-time communication

When to Use WebSockets:

  • Real-time applications (chat, notifications)
  • Live data feeds (stock prices, sports scores)
  • Collaborative features (shared editing)
  • Gaming applications
  • Live streaming

When to Use HTTP:

  • Traditional web applications
  • RESTful APIs
  • File uploads/downloads
  • One-time data requests
  • Caching requirements

Security Considerations

HTTPS

  • TLS/SSL: Encrypts data in transit
  • Certificate Validation: Ensures server identity
  • HSTS: Forces HTTPS connections

CORS (Cross-Origin Resource Sharing)

CORS is a security feature that allows web pages to make requests to different domains. By default, browsers block cross-origin requests for security reasons.

What is “Cross-Origin”?

  • Same origin: https://myapp.comhttps://myapp.com/api
  • Cross-origin: https://myapp.comhttps://api.example.com ❌ (blocked by default)

How CORS Works:

  1. Browser checks if request is cross-origin
  2. For simple requests: Browser sends request directly
  3. For complex requests: Browser sends OPTIONS preflight request first
  4. Server responds with CORS headers allowing/denying the request

Example:

// SIMPLE REQUEST - Browser sends directly
// Only GET, HEAD, POST with basic headers
fetch('https://api.example.com/data');

// COMPLEX REQUEST - Browser sends OPTIONS preflight first
// Uses custom headers, methods other than GET/POST/HEAD
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer token' },
  body: JSON.stringify({ name: 'John' }),
});

// What happens behind the scenes:
// 1. Browser sends: OPTIONS /data (preflight)
// 2. Server responds: Access-Control-Allow-Origin: https://myapp.com
// 3. Browser sends: POST /data (actual request)
// 4. Server responds: Your data

Server Response Headers:

Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

Security Headers

// Prevent clickjacking
X-Frame-Options: DENY

// Prevent MIME type sniffing
X-Content-Type-Options: nosniff

// XSS protection
X-XSS-Protection: 1; mode=block

// Content Security Policy
Content-Security-Policy: default-src 'self'

Common Interview Questions

1. What’s the difference between GET and POST?

  • GET: Retrieves data, repeatable, cacheable, data in URL
  • POST: Submits data, not repeatable, not cacheable, data in body

2. Explain HTTP status codes 200, 201, 204, 400, 401, 403, 404, 500

  • 200: Success
  • 201: Created
  • 204: No Content
  • 400: Bad Request
  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not Found
  • 500: Internal Server Error

3. What is CORS and how does it work?

CORS allows web pages to make requests to different domains. Browser enforces same-origin policy, but CORS headers allow cross-origin requests.

4. Explain HTTP caching mechanisms

  • Browser Cache: Stores resources locally
  • Cache-Control: Controls caching behavior
  • ETags: Validates if resource changed
  • Last-Modified: Time-based validation

5. What are the benefits of HTTP/2?

  • Multiplexing
  • Header compression
  • Server push
  • Binary protocol
  • Stream prioritization

6. Why don’t we only use GET, POST, and DELETE? Why do we need PUT, PATCH, HEAD, and OPTIONS?

While it’s technically possible to use only GET, POST, and DELETE, having specific methods provides several important benefits:

Semantic Clarity

  • Each method clearly communicates intent
  • PUT vs PATCH tells you if it’s a complete or partial update
  • HEAD vs GET indicates if you need the response body

HTTP Standards Compliance

  • RESTful APIs follow HTTP standards for better interoperability
  • Caching works correctly (GET is cacheable, POST isn’t)
  • Browsers and proxies understand the semantics

Performance & Efficiency

  • HEAD saves bandwidth when you only need headers
  • PATCH reduces payload size for partial updates
  • OPTIONS enables CORS preflight without full requests

Safety & Reliability

  • GET and HEAD are safe (no side effects)
  • PUT and DELETE are repeatable (same result when retried)
  • POST and PATCH are not repeatable (prevents accidental duplicates)

Example of why PUT ≠ PATCH:

// PUT - replaces entire resource
PUT /api/users/123
{ "id": 123, "name": "John", "email": "john@example.com", "role": "admin" }

// PATCH - updates only specific fields
PATCH /api/users/123
{ "email": "newemail@example.com" }

Using only GET/POST/DELETE would require encoding this information in the URL or request body, making APIs less intuitive and harder to cache properly.

Practical Examples

Fetch API with Error Handling

async function fetchData(url) {
  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: { Accept: 'application/json', 'Cache-Control': 'no-cache' },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

Handling Different Response Types

async function handleResponse(response) {
  const contentType = response.headers.get('content-type');

  if (contentType?.includes('application/json')) {
    return await response.json();
  } else if (contentType?.includes('text/')) {
    return await response.text();
  } else {
    return await response.blob();
  }
}

Implementing Retry Logic

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;

      if (response.status >= 500 && i < maxRetries - 1) {
        await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, i)));
        continue;
      }

      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, i)));
    }
  }
}

Conclusion

Mastering HTTP is essential for senior frontend developers. Understanding methods, status codes, headers, caching, and security concepts will help you build better applications and excel in technical interviews. Focus on practical implementation and real-world scenarios to demonstrate your expertise.

Remember, HTTP knowledge extends beyond basic concepts - it’s about understanding how the web works at its core and applying that knowledge to solve complex problems efficiently.

Back to Blog