HTTP Methods, Headers, and Status Codes

A Deep Dive into HTTP Protocol Components

Introduction to HTTP Protocol Components

Yesterday, we introduced the HTTP request/response cycle as the foundation for web communication. Today, we'll dive deeper into the specific components that make up HTTP communications: methods (verbs), headers, and status codes.

Understanding these components in depth is crucial for backend developers because they determine how our applications communicate with clients, handle different types of requests, transmit metadata, and indicate the results of operations. Mastering these elements will help you build more robust, secure, and efficient web applications.

mindmap root((HTTP Components)) Methods GET POST PUT PATCH DELETE HEAD OPTIONS Headers Request Headers Response Headers Entity Headers General Headers Status Codes 1xx Informational 2xx Success 3xx Redirection 4xx Client Error 5xx Server Error

The Language Analogy

Think of HTTP as a language for communication between clients and servers, similar to how humans communicate:

Just as fluency in a language requires understanding both vocabulary and grammar, mastery of HTTP requires understanding both the individual components and how they work together in standard patterns.

HTTP Methods (Verbs) In Depth

HTTP methods, sometimes called verbs, define the operation that the client wants to perform on a resource. Let's explore each major method in detail:

GET: Requesting Resources

sequenceDiagram participant Client participant Server Client->>+Server: GET /api/products/123 Note right of Server: Retrieves product information Server->>-Client: 200 OK with product data

// Express.js (Node.js) GET handler
app.get('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  
  // Retrieve product from database
  const product = database.getProduct(productId);
  
  if (!product) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  res.status(200).json(product);
});
        

POST: Creating Resources

sequenceDiagram participant Client participant Server Client->>+Server: POST /api/products
(with product data) Note right of Server: Creates new product Server->>-Client: 201 Created with new product ID

// Express.js (Node.js) POST handler
app.post('/api/products', (req, res) => {
  const productData = req.body;
  
  // Validate input
  if (!productData.name || !productData.price) {
    return res.status(400).json({ error: 'Name and price are required' });
  }
  
  // Create product in database
  const newProduct = database.createProduct(productData);
  
  // Return created product with 201 status
  res.status(201).json({
    id: newProduct.id,
    message: 'Product created successfully',
    product: newProduct
  });
});
        

PUT: Updating Resources (Complete Replacement)

sequenceDiagram participant Client participant Server Client->>+Server: PUT /api/products/123
(with complete product data) Note right of Server: Replaces existing product Server->>-Client: 200 OK with updated product

// Express.js (Node.js) PUT handler
app.put('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  const productData = req.body;
  
  // Validate input
  if (!productData.name || !productData.price || !productData.description) {
    return res.status(400).json({ error: 'Complete product data required' });
  }
  
  // Check if product exists
  const existingProduct = database.getProduct(productId);
  if (!existingProduct) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  // Update product (complete replacement)
  const updatedProduct = database.replaceProduct(productId, productData);
  
  res.status(200).json(updatedProduct);
});
        

PATCH: Partial Updates

sequenceDiagram participant Client participant Server Client->>+Server: PATCH /api/products/123
(with partial product data) Note right of Server: Updates specific fields Server->>-Client: 200 OK with updated product

// Express.js (Node.js) PATCH handler
app.patch('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  const updates = req.body;
  
  // Check if product exists
  const existingProduct = database.getProduct(productId);
  if (!existingProduct) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  // Apply partial updates
  const updatedProduct = database.updateProduct(productId, updates);
  
  res.status(200).json(updatedProduct);
});
        

DELETE: Removing Resources

sequenceDiagram participant Client participant Server Client->>+Server: DELETE /api/products/123 Note right of Server: Removes the product Server->>-Client: 204 No Content

// Express.js (Node.js) DELETE handler
app.delete('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  
  // Check if product exists
  const existingProduct = database.getProduct(productId);
  if (!existingProduct) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  // Remove product
  database.deleteProduct(productId);
  
  // Return 204 No Content
  res.status(204).send();
});
        

Additional Methods

HTTP Headers In Depth

HTTP headers provide additional information about the request or response. They're like metadata that helps clients and servers understand how to process the messages.

Header Categories

General Headers

Apply to both requests and responses:

Request Headers

Sent by the client in HTTP requests:

Response Headers

Sent by the server in HTTP responses:

Entity Headers

Describe the content being transferred:

Security-Related Headers

Modern web applications should include security headers to protect against common attacks:

Working with Headers in Different Backend Languages

Node.js (Express)


// Reading request headers
app.get('/api/products', (req, res) => {
  const acceptHeader = req.header('Accept');
  const userAgent = req.header('User-Agent');
  
  // Setting response headers
  res.set('Content-Type', 'application/json');
  res.set('Cache-Control', 'max-age=3600');
  
  // Security headers
  res.set('Content-Security-Policy', "default-src 'self'");
  res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  
  // Send response
  res.status(200).json({ products: [...] });
});
        

Python (Flask)


from flask import request, jsonify

@app.route('/api/products')
def get_products():
    # Reading request headers
    accept_header = request.headers.get('Accept')
    user_agent = request.headers.get('User-Agent')
    
    response = jsonify({'products': [...]})
    
    # Setting response headers
    response.headers['Content-Type'] = 'application/json'
    response.headers['Cache-Control'] = 'max-age=3600'
    
    # Security headers
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    return response
        

PHP


<?php
// Reading request headers
$acceptHeader = $_SERVER['HTTP_ACCEPT'] ?? '';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

// Setting response headers
header('Content-Type: application/json');
header('Cache-Control: max-age=3600');

// Security headers
header("Content-Security-Policy: default-src 'self'");
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');

// Send response
echo json_encode(['products' => [...]]);
?>
        

HTTP Status Codes In Depth

HTTP status codes are three-digit numbers that indicate the outcome of an HTTP request. They're grouped into five classes, each with a general meaning:

pie title "HTTP Status Code Categories" "1xx: Informational" : 5 "2xx: Success" : 25 "3xx: Redirection" : 15 "4xx: Client Error" : 35 "5xx: Server Error" : 20

1xx: Informational

These status codes indicate that the request was received and the process is continuing.

2xx: Success

These status codes indicate that the request was successfully received, understood, and accepted.

3xx: Redirection

These status codes indicate that further action needs to be taken to complete the request.

4xx: Client Error

These status codes indicate that the client seems to have made an error.

5xx: Server Error

These status codes indicate that the server failed to fulfill a valid request.

Working with Status Codes in Different Backend Languages

Node.js (Express)


app.get('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  
  // Check if product exists
  const product = database.getProduct(productId);
  
  if (!product) {
    // 404: Resource not found
    return res.status(404).json({
      error: 'Product not found',
      code: 'PRODUCT_NOT_FOUND'
    });
  }
  
  // 200: Success
  res.status(200).json(product);
});

app.post('/api/products', (req, res) => {
  try {
    const productData = req.body;
    
    // Validate input
    if (!productData.name || !productData.price) {
      // 400: Bad Request
      return res.status(400).json({
        error: 'Name and price are required',
        code: 'INVALID_INPUT'
      });
    }
    
    // Check authorization
    if (!req.isAdmin) {
      // 403: Forbidden
      return res.status(403).json({
        error: 'Only administrators can create products',
        code: 'PERMISSION_DENIED'
      });
    }
    
    // Check for conflicts
    const existingProduct = database.findProductByName(productData.name);
    if (existingProduct) {
      // 409: Conflict
      return res.status(409).json({
        error: 'A product with this name already exists',
        code: 'PRODUCT_EXISTS'
      });
    }
    
    // Create product
    const newProduct = database.createProduct(productData);
    
    // 201: Created
    res.status(201)
      .location(`/api/products/${newProduct.id}`)
      .json(newProduct);
      
  } catch (error) {
    console.error('Error creating product:', error);
    
    // 500: Internal Server Error
    res.status(500).json({
      error: 'Failed to create product',
      code: 'INTERNAL_ERROR'
    });
  }
});
        

Python (Flask)


from flask import request, jsonify

@app.route('/api/products/', methods=['GET'])
def get_product(product_id):
    # Check if product exists
    product = database.get_product(product_id)
    
    if not product:
        # 404: Resource not found
        return jsonify({
            'error': 'Product not found',
            'code': 'PRODUCT_NOT_FOUND'
        }), 404
    
    # 200: Success
    return jsonify(product), 200

@app.route('/api/products', methods=['POST'])
def create_product():
    try:
        product_data = request.get_json()
        
        # Validate input
        if not product_data.get('name') or not product_data.get('price'):
            # 400: Bad Request
            return jsonify({
                'error': 'Name and price are required',
                'code': 'INVALID_INPUT'
            }), 400
        
        # Check authorization
        if not is_admin():
            # 403: Forbidden
            return jsonify({
                'error': 'Only administrators can create products',
                'code': 'PERMISSION_DENIED'
            }), 403
        
        # Check for conflicts
        existing_product = database.find_product_by_name(product_data['name'])
        if existing_product:
            # 409: Conflict
            return jsonify({
                'error': 'A product with this name already exists',
                'code': 'PRODUCT_EXISTS'
            }), 409
        
        # Create product
        new_product = database.create_product(product_data)
        
        # 201: Created
        response = jsonify(new_product)
        response.status_code = 201
        response.headers['Location'] = f"/api/products/{new_product['id']}"
        return response
        
    except Exception as e:
        app.logger.error(f"Error creating product: {str(e)}")
        
        # 500: Internal Server Error
        return jsonify({
            'error': 'Failed to create product',
            'code': 'INTERNAL_ERROR'
        }), 500
        

PHP


<?php
// Simulate a GET request handler
if ($_SERVER['REQUEST_METHOD'] === 'GET' && preg_match('/\/api\/products\/(\d+)/', $_SERVER['REQUEST_URI'], $matches)) {
    $productId = $matches[1];
    
    // Check if product exists
    $product = $database->getProduct($productId);
    
    if (!$product) {
        // 404: Resource not found
        http_response_code(404);
        echo json_encode([
            'error' => 'Product not found',
            'code' => 'PRODUCT_NOT_FOUND'
        ]);
        exit;
    }
    
    // 200: Success
    http_response_code(200);
    echo json_encode($product);
    exit;
}

// Simulate a POST request handler
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/api/products') {
    try {
        // Get JSON data
        $productData = json_decode(file_get_contents('php://input'), true);
        
        // Validate input
        if (empty($productData['name']) || empty($productData['price'])) {
            // 400: Bad Request
            http_response_code(400);
            echo json_encode([
                'error' => 'Name and price are required',
                'code' => 'INVALID_INPUT'
            ]);
            exit;
        }
        
        // Check authorization
        if (!isAdmin()) {
            // 403: Forbidden
            http_response_code(403);
            echo json_encode([
                'error' => 'Only administrators can create products',
                'code' => 'PERMISSION_DENIED'
            ]);
            exit;
        }
        
        // Check for conflicts
        $existingProduct = $database->findProductByName($productData['name']);
        if ($existingProduct) {
            // 409: Conflict
            http_response_code(409);
            echo json_encode([
                'error' => 'A product with this name already exists',
                'code' => 'PRODUCT_EXISTS'
            ]);
            exit;
        }
        
        // Create product
        $newProduct = $database->createProduct($productData);
        
        // 201: Created
        http_response_code(201);
        header("Location: /api/products/{$newProduct['id']}");
        echo json_encode($newProduct);
        exit;
        
    } catch (Exception $e) {
        error_log("Error creating product: " . $e->getMessage());
        
        // 500: Internal Server Error
        http_response_code(500);
        echo json_encode([
            'error' => 'Failed to create product',
            'code' => 'INTERNAL_ERROR'
        ]);
        exit;
    }
}
?>
        

Best Practices for HTTP Components

HTTP Methods Best Practices

HTTP Headers Best Practices

HTTP Status Codes Best Practices

Practical Exercise: API Design with HTTP Components

Task: Design a RESTful API for a Blog Platform

For this exercise, you'll design a RESTful API for a blog platform, focusing on the proper use of HTTP methods, headers, and status codes. The blog platform should support posts, comments, and user management.

Requirements:

  1. Design endpoints for the following resources:
    • Posts (create, read, update, delete)
    • Comments (create, read, update, delete)
    • Users (create, read, update, delete)
  2. For each endpoint, specify:
    • HTTP method
    • URL pattern
    • Required and optional headers
    • Request body format (if applicable)
    • Response status codes for success and error scenarios
    • Response body format
  3. Implement at least one example of each of the following:
    • Pagination
    • Filtering
    • Sorting
    • Error handling
    • Authentication
  4. Include appropriate caching and security headers

Deliverable:

Create a document or diagram detailing your API design. For at least two endpoints, provide complete request and response examples, including headers and bodies.

Example to get you started:


Endpoint: Get All Posts
- Method: GET
- URL: /api/posts
- Query Parameters:
  - page (optional): Page number for pagination (default: 1)
  - limit (optional): Number of posts per page (default: 10)
  - sort (optional): Field to sort by (default: 'createdAt')
  - order (optional): 'asc' or 'desc' (default: 'desc')
  - category (optional): Filter by category
- Required Headers:
  - None
- Optional Headers:
  - Authorization: Bearer token for personalized content
- Success Response:
  - Status Code: 200 OK
  - Body: 
    {
      "posts": [...],
      "total": 42,
      "page": 1,
      "limit": 10,
      "totalPages": 5
    }
- Error Responses:
  - Status Code: 400 Bad Request (invalid query parameters)
  - Status Code: 401 Unauthorized (invalid or expired token)
  - Status Code: 500 Internal Server Error
- Response Headers:
  - Content-Type: application/json
  - Cache-Control: max-age=60
          

Summary

In this session, we've explored the core components of HTTP in depth:

Understanding these components is crucial for building effective web applications and APIs. By using the right method, including appropriate headers, and returning meaningful status codes, you can create interfaces that are:

In our next session, we'll build on this knowledge to explore RESTful API design principles, examining how to structure resources, handle relationships, and implement common patterns for web APIs.

Additional Resources