>_>_
Creating Your Own ExpressJS from Scratch (Part 2) - Middlewares and Controllers

Creating Your Own ExpressJS from Scratch (Part 2) - Middlewares and Controllers

In Part 1, we laid the foundation of our custom Node.js web framework by implementing a basic routing system. In this part, we’ll build on that by adding support for middlewares and controllers — two fundamental building blocks in any modern web framework.

🧪 What You Will Learn

  • Understand the middleware pattern and how it applies to route handling
  • Chain multiple functions (middlewares and final controller) for each route
  • Enhance your routing system to execute handlers sequentially

🧱 Middleware Pattern: Concept Overview

Middlewares are functions that receive a request, response, and a next function. Calling next() moves control to the next middleware or controller in the chain.

If a middleware ends the response (e.g., res.end()), the chain is halted. This pattern is common in frameworks like ExpressJS and enables modular, reusable behavior.


🛠️ Adding Middleware Support

Let’s implement middleware chaining into the existing server.

src/app.js

Add these functions inside the App function:

// Dispatches the handler chain
const dispatchChain = (req, res, handlers) => {
  return invokeMiddlewares(req, res, handlers);
};

// Recursive function to process each middleware/controller
const invokeMiddlewares = async (req, res, handlers) => {
  if (!handlers.length) return;
  const current = handlers[0];
  return current(req, res, async () => {
    await invokeMiddlewares(req, res, handlers.slice(1));
  });
};

Now modify your serverHandler to use this chain:

const serverHandler = async (req, res) => {
  const sanitizedUrl = sanitizeUrl(req.url, req.method);
  const match = matchUrl(sanitizedUrl);

  if (match) {
    const handlers = routes.get(match);
    await dispatchChain(req, res, [...handlers]);
  } else {
    res.statusCode = 404;
    res.end('Not found');
  }
};

That's all you need to support middleware chaining! 🎉


🧪 Testing Your Middleware Logic

Update your index.js file to define and test some middleware functions.

const App = require('./src/app');
const app = App();

// Define test middlewares and controller
const mid1 = (req, res, next) => {
  console.log('Middleware 1');
  next();
};

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

const controller = (req, res) => {
  console.log('Final controller');
  res.end('Hello from controller!');
};

// Add middleware + controller chain to a route
app.get('/middleware', mid1, mid2, controller);

// Add more routes if needed
app.get('/test', (req, res) => res.end('Test route'));

const start = async () => {
  app.run(3000);
};

start();

Run it:

node index.js

Visit:

GET http://localhost:3000/middleware

Output:

Middleware 1
Middleware 2
Final controller

📚 Summary

  • Middleware chaining allows you to break logic into reusable steps
  • You implemented the Express-style next() pattern
  • Each route now supports any number of middlewares plus a final controller

✅ This prepares the framework for adding advanced features like logging, validation, authentication, etc.


▶️ Coming Up Next

In Part 3, we’ll add support for route parameters and dynamic paths — another key feature of full-featured web frameworks.

Stay tuned and happy coding! 🚀