Skip to main content

Middleware

Middlewares in Apex​

Middleware functions are a core part of Apex, allowing you to process requests before they reach route handlers. Apex includes several built-in middleware functions and supports custom middleware creation.

Understanding Middleware​

Middleware functions have access to the request object (req), the response object (res), and the next middleware function (next) in the application's request-response cycle. They can:

  • Execute any code
  • Make changes to the request and response objects
  • End the request-response cycle
  • Call the next middleware function in the stack

Using Built-in Middleware​

Body Parser​

Parses request bodies in different formats:

// Apply body parser middleware
router.use(apex.bodyParser());

router.post("/api/data", (req, res) => {
// req.body contains the parsed body
console.log("Received data:", req.body);
res.json({ received: true, data: req.body });
});

The body parser handles:

  • application/json
  • application/x-www-form-urlencoded
  • text/plain

Static Files​

Serve static files from a directory:

// Serve static files from the 'public' directory
router.use(apex.static("public"));

Favicon​

Serve a favicon file:

// Serve favicon
router.use(apex.favicon("./public/favicon.ico"));

Rate Limiter​

Limit request rates from clients:

// Basic rate limiting
router.use(apex.rateLimit());

// default rateLimit options
// windowMs = 60 * 1000, // 1 minute
// max = 100, // 100 requests per window
// message = "Too many requests, please try again later.",
// statusCode = 429, // 429 Too Many Requests
// skip = () => false, // Skip rate limiting for certain requests
// keyGenerator = (req) => req.ip || "global", // Default key generator (IP-based)

const limiter = apex.rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 2, // 100 requests per windowMs
message: "Too many requests from this IP, please try again later",
statusCode: 429,
headers: true,
});

// Custom rate limiting on specific routes
router.use("/api", limiter);

router.get("/api/data", (req, res) => {
res.json({ message: "API response" });
});

// on single route
router.get("/api", limiter, (req, res) => {
res.send("rate limit");
});

Advanced rate limiting options:

// Skip rate limiting for certain requests
router.use(
apex.rateLimit({
skip: (req) => {
// Skip rate limiting for admin users
return req.headers["x-admin-key"] === "your-secret-admin-key";
},
})
);

// Custom key generator (limit by user ID instead of IP)
router.use(
apex.rateLimit({
keyGenerator: (req) => {
// Use user ID from request for rate limiting
return req.headers["x-user-id"] || req.ip;
},
})
);

// Custom handler for rate-limited requests
router.use(
apex.rateLimit({
handler: (req, res) => {
res.status(429).json({
error: "Rate limit exceeded",
retryAfter: res.getHeader("Retry-After"),
});
},
})
);

Cors​

Enable Cross-Origin Resource Sharing (CORS) for your API endpoints:

// Basic usage (enable all CORS requests)
router.use(apex.cors());

// With configuration options
router.use(
apex.cors({
origin: "https://yourdomain.com", // or array of allowed origins
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 86400, // 24 hours
})
);

// Dynamic origin control
router.use(
apex.cors({
origin: (req, callback) => {
const allowedOrigins = ["https://app.yourdomain.com", "https://admin.yourdomain.com"];
const origin = req.headers.origin;
callback(null, allowedOrigins.includes(origin));
},
})
);

// Route-specific CORS
router.get("/api/data", apex.cors({ origin: "https://specific-domain.com" }), (req, res) => {
res.json({ data: "Protected CORS data" });
});

Flash Messages​

Enable flash messages for temporary storage between requests:

// Enable flash middleware
router.use(apex.useFlash());

router.post("/login", (req, res) => {
// Authentication logic...

// Set flash message
req.flash("success", "You have been logged in successfully.");
res.redirect("/dashboard");
});

router.get("/dashboard", (req, res) => {
// Get flash messages
const successMessages = req.flash("success");
const errorMessages = req.flash("error");

res.render("dashboard", {
success: successMessages,
errors: errorMessages,
});
});

Creating Custom Middleware​

Basic Middleware​

// Logger middleware
const logger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next();
};

// Apply the middleware
router.use(logger);

router.get("/", (req, res) => {
res.send("Hello World");
});

Authentication Middleware​

// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;

if (!token || !token.startsWith("Bearer ")) {
return res.status(401).json({ error: "Authentication required" });
}

const apiKey = token.split(" ")[1];

if (apiKey !== "valid-api-key") {
return res.status(403).json({ error: "Invalid or expired token" });
}

// Add user info to request object
req.user = { id: "123", role: "admin" };

next();
};

// Apply to specific routes
router.use("/api/protected", authenticate);

router.get("/api/protected/data", (req, res) => {
res.json({
message: "Protected data",
user: req.user,
});
});

Error Handling Middleware​

// Route that might throw an error
router.get("/might-error", (req, res) => {
const random = Math.random();
if (random > 0.5) {
throw new Error("Something went wrong");
}
res.json({ success: true });
});

// Error handling middleware (must have 4 parameters)
const errorHandler = (err, req, res, next) => {
console.error("Error:", err.message);
res.status(500).json({
error: {
message: err.message,
timestamp: new Date().toISOString(),
},
});
};

// Add error handling middleware last
router.use(errorHandler);

Request Timing Middleware​

// Request timing middleware
const requestTimer = (req, res, next) => {
const start = Date.now();

// Add listener for when response is finished
res.on("finish", () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`);
});

next();
};

router.use(requestTimer);

// Routes
router.get("/fast", (req, res) => {
res.send("Fast response");
});

router.get("/slow", (req, res) => {
setTimeout(() => {
res.send("Slow response");
}, 1000);
});

Request Validation Middleware​

// Apply body parser
router.use(apex.bodyParser());

// Validation middleware for user creation
const validateUser = (req, res, next) => {
const { username, email, password } = req.body;
const errors = [];

if (!username || username.length < 3) {
errors.push("Username must be at least 3 characters");
}

if (!email || !email.includes("@")) {
errors.push("Valid email is required");
}

if (!password || password.length < 6) {
errors.push("Password must be at least 6 characters");
}

if (errors.length > 0) {
return res.status(400).json({ errors });
}

next();
};

// Apply validation to specific route
router.post("/users", validateUser, (req, res) => {
// Create user...
res.status(201).json({ message: "User created" });
});

Conditional Middleware​

// Only apply to production environment
const securityHeaders = (req, res, next) => {
if (process.env.NODE_ENV === "production") {
res.set({
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Content-Security-Policy": "default-src 'self'",
});
}
next();
};

router.use(securityHeaders);

Middleware Chaining​

Multiple middleware functions can be chained together:

// Logger middleware
const logger = (req, res, next) => {
console.log(`Request: ${req.method} ${req.path}`);
next();
};

// Authentication check
const authCheck = (req, res, next) => {
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ error: "Authentication required" });
}
next();
};

// Role check
const adminOnly = (req, res, next) => {
// Assume auth middleware has set req.query
if (req.query && req.query.apikey !== "admin") {
return res.status(403).json({ error: "Admin access required" });
}
next();
};

// Chain middlewares for specific routes
router.get("/admin/dashboard", logger, authCheck, adminOnly, (req, res) => {
res.json({ message: "Admin dashboard" });
});

Best Practices for Middleware​

  1. Order matters: Apply middleware in the correct order (e.g., body parsing before validation)
  2. Keep middleware focused: Each middleware should have a single responsibility
  3. Handle errors properly: Use try/catch in middleware and pass errors to next
  4. Avoid unnecessary middleware: Only apply middleware where needed