Skip to content

Latest commit

 

History

History
536 lines (378 loc) · 16.1 KB

File metadata and controls

536 lines (378 loc) · 16.1 KB

Topic 006: Different types of middleware implementation in Node.jS.

Topic 006: Different types of middleware implementation in Node.js.


What is Middleware in Node Js?

  • Middleware in Node.js refers to a concept where functions can be used to process incoming requests before they reach their final destination and handle outgoing responses before they are sent back to the client. These functions sit in between the initial request and the final response, hence the term “middleware.”

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 ?

  1. The request passes through all middleware functions in the chain.

  2. 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.


1. Logging Middleware

  • Purpose: To log details about each incoming request, including the method, URL, and response time.

  • 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);

2. Authentication Middleware

  • Purpose: To protect routes by ensuring that only authenticated users can access certain endpoints.

  • JWT Authentication: Middleware to verify JSON Web Tokens in requests to secure endpoints.

  • OAuth Middleware: Implemented middleware to handle OAuth flows, such as with Google or Facebook login.

  • 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);
  • Here’s a simplified example of JWT authentication middleware in Node.js using the 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;

3. Error Handling Middleware

  • Purpose: To handle errors centrally and return a consistent error response format.

  • Example:

    const errorHandler = (err, req, res, next) => {
      console.error(err.message, err);
      res.status(500).send("Something failed.");
    };
    
    app.use(errorHandler);

4. Body Parsing Middleware

  • Purpose: To parse incoming request bodies so they can be easily accessed in route handlers.

  • Example:

    const express = require("express");
    const bodyParser = require("body-parser");
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

5. CORS Middleware

  • Purpose: To enable Cross-Origin Resource Sharing (CORS) for allowing resources to be requested from another domain.

  • Example:

    const cors = require("cors");
    
    const corsOptions = {
      origin: "http://example.com",
      optionsSuccessStatus: 200,
    };
    
    app.use(cors(corsOptions));

6. Rate Limiting Middleware

  • Purpose: To limit the number of requests from a single IP address to prevent abuse or DDoS attacks.

  • 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);

7. Static File Serving Middleware

  • Purpose: To serve static files such as images, CSS files, and JavaScript files.

  • Example:

    const express = require("express");
    
    app.use(express.static("public"));

8. Request Validation Middleware

  • Purpose: To validate incoming request data to ensure it meets the expected format and constraints.

  • 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
    });

9. Compression Middleware

  • Middleware like 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:

Step-by-Step Implementation:

  1. Install the compression package: You need to install the compression package from npm. You can do this using the following command:

    npm install compression
  2. 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}`);
    });

Explanation:

  • 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.

Additional Configuration (Optional):

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
  })
);
  • This middleware can greatly enhance the performance of your web application, especially if your application serves large amounts of data or if it is accessed frequently by users with slow internet connections.

10. Query Parsing Middleware

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:

  • We define a middleware function called parseQuery that extracts the name parameter from the query string. If the name parameter is missing, the middleware sends a 400 Bad Request response.
  • The parsed query parameter (in this case, just name) is attached to the req object for easy access in subsequent route handlers.
  • We apply the parseQuery middleware to all routes using app.use(parseQuery).
  • In the /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:

11. Custom Middleware for Business Logic

Middleware for Role-Based Access Control:

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.");
});

12. Request Parsing Middleware

Body Parsers:

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());

Query String Parsers:

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);

13. Logging Middleware

Using morgan for Request Logging:

const morgan = require("morgan");
app.use(morgan("combined"));

Custom Logging Middleware:

const customLogger = (req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
};

app.use(customLogger);

14. Validation Middleware

Using 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
});

15. Security Middleware

Helmet Middleware for Setting Security Headers:

const helmet = require("helmet");
app.use(helmet());

CORS Middleware for Cross-Origin Requests:

const cors = require("cors");

const corsOptions = {
  origin: "http://example.com",
  optionsSuccessStatus: 200,
};

app.use(cors(corsOptions));

CSRF Protection:

const csrf = require("csurf");
const csrfProtection = csrf({ cookie: true });

app.get("/payment", csrfProtection, (req, res) => {
  // Render payment form with CSRF token
});

16. Session Management Middleware

Using 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" }),
  })
);

17. Cookie Management Middleware

Using cookie-parser for cookie Management::

  • Parse cookie header and populate req.cookies.
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.