codex-lv4-may-2025

Level Navigation: 1 2 3 (4ℹ️) (5ℹ️) 6 7 8 9 10 11 12 13 14⚡ 15⚡ (16ℹ️) 17 18 19 20 21 22 23 24 25 26⚡ 27⚡ 28⚡ 29 30 31 32 33 34 35 36 37 38 39⚡ 40⚡

Level 30: Harden — Validate and Handle Errors

Goal

Add validation and consistent error handling to your API.

What to Do

  1. Validate incoming POST data and return 400 on bad input.
  2. Centralize errors in a helper or middleware that responds with { "error": "message" }.
  3. Optional booster:
    • Add a timing log for each request.
Show Me: comprehensive validation

// Validate multiple required fields
app.post('/items', (req, res) => {
  if (!req.body?.title) {
    return res.status(400).json({ error: 'Title is required' });
  }
  if (!req.body?.price) {
    return res.status(400).json({ error: 'Price is required' });
  }
  if (typeof req.body.price !== 'number' || req.body.price < 0) {
    return res.status(400).json({ error: 'Price must be a positive number' });
  }
  
  // If validation passes, create the item
  const newItem = { ...req.body, id: randomUUID() };
  itemsStorage.push(newItem);
  res.status(201).json(newItem);
});
Show Me: centralized error helper

// Create a helper function for consistent error responses
function sendError(res, statusCode, message) {
  return res.status(statusCode).json({ error: message });
}

// Use it in your routes
app.post('/items', (req, res) => {
  if (!req.body?.title) {
    return sendError(res, 400, 'Title is required');
  }
  // ... rest of route
});

app.get('/items/:id', (req, res) => {
  const item = itemsStorage.find(entry => entry.id === req.params.id);
  if (!item) {
    return sendError(res, 404, 'Item not found');
  }
  res.json(item);
});
Show Me: request timing log (optional)

// Add timing middleware before your routes
app.use((req, res, next) => {
  const start = Date.now();
  
  // Log after response is sent
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`);
  });
  
  next();
});

Digging Deeper: Middleware in Express

What is middleware?

Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. Middleware can execute code, make changes to the request and response objects, end the request-response cycle, or call the next middleware function.

The Request-Response Cycle:

When a request comes in, Express passes it through middleware functions in the order they are defined. Each middleware can:

  1. Execute code (like logging, timing, authentication)
  2. Modify the request or response (like parsing JSON, adding headers)
  3. End the cycle (by sending a response)
  4. Call next() to pass control to the next middleware

Example: How express.json() Works

// This middleware runs for every request
app.use(express.json());

// What it does:
// 1. Checks if request has Content-Type: application/json
// 2. Reads the request body
// 3. Parses it from JSON string to JavaScript object
// 4. Attaches it to req.body
// 5. Calls next() to continue to your route handlers

Types of Middleware:

  1. Application-level middleware (runs for all routes):
    app.use(express.json()); // Parse JSON bodies
    app.use((req, res, next) => {
      console.log('Request received:', req.method, req.path);
      next();
    });
    
  2. Route-level middleware (runs for specific routes):
    app.post('/items', validateItem, (req, res) => {
      // validateItem is middleware that runs before this handler
    });
    
  3. Error-handling middleware (runs when errors occur):
    app.use((err, req, res, next) => {
      console.error(err);
      res.status(500).json({ error: 'Something went wrong' });
    });
    

Why Use Middleware?

Common Middleware Patterns: