Topic 006: Different types of middleware implementation in Node.jS.
What is Middleware in Node Js?
How Does Node.js Middleware Pattern Work?
In Node.js, middleware functions are essentially functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle.
When a request is made to the server, it passes through a series of middleware functions before reaching the final route handler or endpoint.
Each middleware function can perform its task and either pass the request to the next middleware function using the next function or terminate the request-response cycle by sending a response.
What happens when the request reaches the last middleware in the chain ?
The request passes through all middleware functions in the chain.
If none of the middleware functions send a response back to the client (i.e., they all call the next() function to pass control to the next middleware or route handler):
Express looks for a matching route handler based on the request URL and HTTP method.
If a matching route handler is found, it is executed
If no matching route handler is found, Express sends a default “Not Found” response back to the client with a status code of 404.
What is next() ?
In Express.js, next() is a function that is used within middleware functions to pass control to the next middleware function in the chain. When next() is called within a middleware function, Express moves to the next middleware function defined in the application.
When a middleware function is defined, it typically receives three arguments: req (the request object), res (the response object), and next (the next middleware function in the chain). By calling next() within a middleware function, the control is passed to the subsequent middleware function.
Middleware Chaining
Generally, a set of middlewares are chained to form a set of functions that execute one after the other in order.
The next() function is called at the end of every middleware to pass the control to the next middleware. The last middleware function sends back the response to the client. Hence, different middleware process the request before the response is sent back.
Example:
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} took ${duration}ms`);
});
next();
};
app.use(logger);
Example:
const authenticate = (req, res, next) => {
const token = req.header("Authorization");
if (!token) {
return res.status(401).send("Access denied. No token provided.");
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (ex) {
res.status(400).send("Invalid token.");
}
};
app.use("/api/protected", authenticate);
jsonwebtoken
library:const jwt = require("jsonwebtoken");
function authenticateToken(req, res, next) {
const token = req.header("Authorization") && req.header("Authorization").split(" ")[1];
if (!token) return res.sendStatus(401); // Unauthorized
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Forbidden
req.user = user;
next();
});
}
module.exports = authenticateToken;
Example:
const errorHandler = (err, req, res, next) => {
console.error(err.message, err);
res.status(500).send("Something failed.");
};
app.use(errorHandler);
Example:
const express = require("express");
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Example:
const cors = require("cors");
const corsOptions = {
origin: "http://example.com",
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));
Example:
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.use(limiter);
Example:
const express = require("express");
app.use(express.static("public"));
Example:
const { check, validationResult } = require("express-validator");
const validateUser = [
check("email").isEmail().withMessage("Must be a valid email"),
check("password").isLength({ min: 5 }).withMessage("Password must be at least 5 chars long"),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
app.post("/register", validateUser, (req, res) => {
// Handle registration
});
compression
to gzip responses, reducing the payload size and improving performance.-Certainly! Compression middleware is used to reduce the size of the response body, which can help speed up web applications by reducing the amount of data that needs to be transferred over the network. In Node.js, this can be achieved using the compression
middleware.
Here is an example of how to implement compression middleware in a Node.js application using Express:
Install the compression
package:
You need to install the compression
package from npm. You can do this using the following command:
npm install compression
Set up the middleware in your Express application: Once the package is installed, you can use it in your Express application as shown below:
const express = require("express");
const compression = require("compression");
const app = express();
// Use compression middleware
app.use(compression());
// Example route to test compression
app.get("/", (req, res) => {
res.send("Hello, this is a compressed response!");
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Importing the necessary modules:
const express = require("express");
const compression = require("compression");
Creating an instance of the Express application:
const app = express();
Using the compression middleware:
app.use(compression());
This line adds the compression middleware to your application, which will compress all HTTP responses by default.
Defining a route to test compression:
app.get("/", (req, res) => {
res.send("Hello, this is a compressed response!");
});
You can visit this route in your browser or use a tool like curl
to see the compressed response.
Starting the server:
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
This code starts the server on the specified port.
You can configure the compression
middleware by passing options. For example, you might want to set a threshold for compressing responses only if their size exceeds a certain number of bytes.
app.use(
compression({
threshold: 1024, // Compress responses only if their size is > 1KB
})
);
Query string parsers are middleware functions in Node.js applications, particularly in frameworks like Express, used to parse and validate query parameters attached to the URL. These parameters are typically appended to the end of a URL after a question mark (?) and are key-value pairs separated by ampersands (&).
The query string parser middleware parses these parameters from the URL and makes them available to your application’s route handlers. Additionally, you can implement validation logic within the middleware to ensure that the query parameters meet certain criteria or constraints.
Here’s an example of how to implement a query string parser middleware with validation using Express:
const express = require("express");
const app = express();
// Query string parser middleware
const parseQuery = (req, res, next) => {
// Assuming we expect a 'name' parameter in the query string
const name = req.query.name;
// Validation logic
if (!name) {
return res.status(400).send("Name parameter is required.");
}
// Optionally, you can further validate the parameter's format or values
// Attach the parsed query parameter to the request object for easy access in route handlers
req.parsedQuery = { name };
// Call next to pass control to the next middleware or route handler
next();
};
// Apply the query string parser middleware to all routes
app.use(parseQuery);
// Example route that uses the parsed query parameter
app.get("/greet", (req, res) => {
const { name } = req.parsedQuery;
res.send(`Hello, ${name}!`);
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
In this example:
parseQuery
that extracts the name
parameter from the query string. If the name
parameter is missing, the middleware sends a 400 Bad Request response.name
) is attached to the req
object for easy access in subsequent route handlers.parseQuery
middleware to all routes using app.use(parseQuery)
./greet
route handler, we access the parsed query parameter (name
) from req.parsedQuery
and use it to send a personalized greeting response.This is a basic example, but you can extend it to handle more complex query parameters or implement additional validation logic as needed for your application.
Certainly! Here’s how you can implement each of the mentioned middleware topics with code:
const checkUserRole = (req, res, next) => {
// Assuming user role is stored in req.user.role
if (req.user.role !== "admin") {
return res.status(403).send("Access denied. You are not authorized to access this resource.");
}
next();
};
// Applying middleware to a specific route
app.get("/admin/dashboard", checkUserRole, (req, res) => {
res.send("Welcome to the admin dashboard.");
});
const bodyParser = require("body-parser");
// Parsing JSON bodies
app.use(bodyParser.json());
// Parsing URL-encoded bodies
app.use(bodyParser.urlencoded({ extended: true }));
// Parsing multipart/form-data
const multer = require("multer");
const upload = multer();
app.use(upload.none());
const parseQuery = (req, res, next) => {
const name = req.query.name;
if (!name) {
return res.status(400).send("Name parameter is required.");
}
req.parsedQuery = { name };
next();
};
app.use(parseQuery);
morgan
for Request Logging:const morgan = require("morgan");
app.use(morgan("combined"));
const customLogger = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
};
app.use(customLogger);
express-validator
for Request Validation:const { body, validationResult } = require("express-validator");
const validateRequest = [
body("email").isEmail(),
body("password").isLength({ min: 6 }),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
app.post("/register", validateRequest, (req, res) => {
// Register user
});
const helmet = require("helmet");
app.use(helmet());
const cors = require("cors");
const corsOptions = {
origin: "http://example.com",
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));
const csrf = require("csurf");
const csrfProtection = csrf({ cookie: true });
app.get("/payment", csrfProtection, (req, res) => {
// Render payment form with CSRF token
});
express-session
for Session Management:const session = require("express-session");
const MongoStore = require("connect-mongo")(session);
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: new MongoStore({ url: "mongodb://localhost/sessions" }),
})
);
cookie-parser
for cookie Management::var express = require("express");
var cookieParser = require("cookie-parser");
var app = express();
app.use(cookieParser());
app.get("/", function (req, res) {
// Cookies that have not been signed
console.log("Cookies: ", req.cookies);
// Cookies that have been signed
console.log("Signed Cookies: ", req.signedCookies);
});
app.listen(8080);
// curl command that sends an HTTP request with two cookies
// curl http://127.0.0.1:8080 --cookie "Cho=Kim;Greet=Hello"
This code provides examples of each middleware type with their implementation in a Node.js/Express application. You can adapt and customize them according to your specific requirements and business logic.
These are some of the middleware implementations that I have worked on in my projects, each serving a specific purpose to enhance the functionality, security, and maintainability of the applications.