Middleware System

Novaxjs2 provides a flexible middleware system for request processing, error handling, and response transformation. Middleware functions have access to the request and response objects and can execute any code, make changes to these objects, or end the request-response cycle.

Standard Middleware

Global middleware that runs for every request:

// Logging middleware
app.useMiddleware((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next(); // Call next middleware
});

// Authentication middleware
app.useMiddleware((req, res, next) => {
  const token = req.headers.authorization;
  if (token === 'secret-token') {
    req.user = { id: 1, name: 'John Doe' };
    next();
  } else {
    res.status(401).end('Unauthorized');
  }
});

// Body parsing middleware (built-in)
// JSON and form data parsing is automatic in Novaxjs2

Error Middleware

Middleware that handles errors in the application:

// Error handling middleware
app.useErrorMiddleware((err, req, res, next) => {
  console.error('Error:', err.stack);
  
  // Custom error responses based on error type
  if (err.statusCode === 404) {
    return res.status(404).end('Page not found');
  }
  
  res.status(500).end('Internal Server Error');
});

// Async error handling
app.useErrorMiddleware(async (err, req, res, next) => {
  // Log error to database or external service
  await logErrorToService(err, req);
  
  res.status(500).json({
    error: 'Something went wrong',
    requestId: req.id // Custom request ID
  });
});

Built-in Middleware

Novaxjs2 includes several built-in middleware functions:

// Static file serving
app.serveStatic('public'); // Serve files from 'public' directory

// Automatic body parsing for:
// - JSON (application/json)
// - Form data (application/x-www-form-urlencoded)
// - Multipart form data (multipart/form-data) for file uploads

// CORS support
app.cors({
  origins: ['https://example.com', 'http://localhost:3000'],
  methods: 'GET, POST, PUT, DELETE, OPTIONS',
  headers: 'Content-Type, Authorization',
  credentials: true
});

Middleware Execution Order

Middleware executes in the order they are defined. Understanding the execution flow is crucial:

app.useMiddleware((req, res, next) => {
  console.log('Middleware 1');
  next();
});

app.useMiddleware((req, res, next) => {
  console.log('Middleware 2');
  next();
});

app.get('/', (req, res) => {
  console.log('Route handler');
  return 'Hello World';
});

// Execution order:
// 1. Middleware 1
// 2. Middleware 2
// 3. Route handler

Middleware Best Practices

Route-Specific Middleware

Novaxjs2 supports adding middleware to specific routes, allowing for fine-grained control over request processing.

Adding Middleware to Routes

Apply middleware to individual routes:

// Middleware function
function logRequest(req, res, next) {
  console.log(`Request: ${req.method} ${req.url}`);
  next();
}

function requireAuth(req, res, next) {
  if (!req.headers.authorization) {
    return res.status(401).end('Authentication required');
  }
  next();
}

// Route with middleware
app.get('/protected', logRequest, requireAuth, (req, res) => {
  return 'This route is protected by middleware';
});

// POST route with middleware
app.post('/api/data', logRequest, requireAuth, (req, res) => {
  return { message: 'Data processed successfully' };
});

Multiple Middleware

Chain multiple middleware functions together:

// Validation middleware
function validateUserData(req, res, next) {
  const { username, email } = req.body;
  if (!username || !email) {
    return res.status(400).end('Username and email are required');
  }
  next();
}

// Sanitization middleware
function sanitizeData(req, res, next) {
  if (req.body.username) {
    req.body.username = req.body.username.trim();
  }
  next();
}

// Rate limiting middleware
function rateLimit(req, res, next) {
  const ip = req.ip;
  const requests = requestCount[ip] || 0;
  if (requests > 100) {
    return res.status(429).end('Too many requests');
  }
  requestCount[ip] = requests + 1;
  next();
}

app.post('/register', 
  rateLimit,
  validateUserData,
  sanitizeData,
  (req, res) => {
    // Process registration
    return { message: 'User registered successfully' };
  }
);

Async Middleware

Middleware can be asynchronous for database operations or API calls:

// Async middleware for database operations
async function loadUser(req, res, next) {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) {
      return res.status(404).end('User not found');
    }
    req.user = user;
    next();
  } catch (error) {
    next(error);
  }
}

// Async middleware for API calls
async function fetchExternalData(req, res, next) {
  try {
    const response = await fetch('https://api.example.com/data');
    req.externalData = await response.json();
    next();
  } catch (error) {
    next(error);
  }
}

app.get('/user/:userId/profile',
  loadUser,
  fetchExternalData,
  (req, res) => {
    return {
      user: req.user,
      externalData: req.externalData
    };
  }
);

Error Handling in Route Middleware

Proper error handling in route-specific middleware:

// Error-prone middleware
function riskyOperation(req, res, next) {
  try {
    // Some operation that might fail
    if (Math.random() > 0.5) {
      throw new Error('Something went wrong');
    }
    next();
  } catch (error) {
    next(error); // Pass error to error middleware
  }
}

// Alternatively, use async/await with try/catch
async function asyncRiskyOperation(req, res, next) {
  try {
    await someAsyncOperation();
    next();
  } catch (error) {
    next(error);
  }
}

app.get('/risky', riskyOperation, (req, res) => {
  return 'This might fail sometimes';
});