Newer
Older
express-blog / src / middleware / index.js
// src/setupMiddleware.js
const express = require("express");
const bodyParser = require("body-parser");
const compression = require("compression");

const routes = require("../routes");
const formatHtml = require("./formatHtml");
const logEvent = require("./analytics.js");
const { applyProductionSecurity } = require("./applyProductionSecurity");
const validateRequestIntegrity = require("./validateRequestIntegrity");
const errorHandler = require("./errorHandler");
const baseContext = require("./baseContext");
const hbs = require("./hbs");
const authCheck = require("./authCheck");
const { redirectMiddleware } = require("./redirect");
const { winstonLogger } = require("../utils/logging");

const {
  TRUST_PROXY,
  EXCLUDED_PATHS,
  DATA_LIMIT_BYTES,
  RAW_BODY_LIMIT_BYTES,
  RAW_BODY_TYPE,
  FALLBACK_ENCODING,
  FALLBACK_BODY,
} = require("../constants/middlewareConstants");

const {
  loggingMiddleware,
  morganInfo,
  morganWarn,
  morganError,
} = require("./logging");

function setupApp() {
  const app = express();

  app.disable("x-powered-by");
  app.set("trust proxy", TRUST_PROXY);

  // Body parsing with different limits for excluded vs normal paths
  app.use((req, res, next) => {
    const isExcludedPath = EXCLUDED_PATHS.includes(req.path);
    const limit = isExcludedPath ? RAW_BODY_LIMIT_BYTES : DATA_LIMIT_BYTES;

    const contentType = req.get("content-type") || "";

    if (contentType.includes("application/json")) {
      // Parse JSON with appropriate limit
      express.json({ limit })(req, res, (err) => {
        if (err) {
          winstonLogger.error("JSON parsing error:", err.message);
          return next(err);
        }
        winstonLogger.debug("Parsed JSON body:", req.body);
        next();
      });
    } else if (contentType.includes("application/x-www-form-urlencoded")) {
      // Parse form data with appropriate limit
      express.urlencoded({ extended: false, limit })(req, res, (err) => {
        if (err) {
          winstonLogger.error("Form parsing error:", err.message);
          return next(err);
        }
        winstonLogger.debug("Parsed form body:", req.body);
        next();
      });
    } else if (contentType.includes("multipart/form-data")) {
      // For multipart, we'd need multer or similar, but pass through for now
      winstonLogger.debug(
        "Multipart form detected - may need additional handling"
      );
      next();
    } else {
      // Try form parsing first (most common for HTML forms), then JSON
      express.urlencoded({ extended: false, limit })(req, res, (formErr) => {
        if (formErr) {
          winstonLogger.warn(
            "Form parsing failed, trying JSON:",
            formErr.message
          );
          express.json({ limit })(req, res, (jsonErr) => {
            if (jsonErr) {
              winstonLogger.error("Both parsers failed:", {
                formErr: formErr.message,
                jsonErr: jsonErr.message,
              });
              return next(jsonErr);
            }
            winstonLogger.warn("Parsed JSON body (fallback):", req.body);
            next();
          });
        } else {
          winstonLogger.debug("Parsed form body (default):", req.body);
          next();
        }
      });
    }
  });

  app.use(hbs);

  // Setup logging
  app.use(logEvent, morganInfo, morganWarn, morganError, loggingMiddleware);

  app.use(authCheck);

  // Setup handlebars
  app.use(baseContext);

  // Setup production environment
  if (
    process.env.NODE_ENV === "production" ||
    process.env.NODE_ENV === "testing"
  ) {
    app.use(applyProductionSecurity);
  }

  app.use(compression());
  app.use(validateRequestIntegrity);
  app.use(formatHtml);
  app.use(redirectMiddleware);
  app.use(routes);
  app.use(errorHandler);

  return app;
}

module.exports = setupApp;