Course:Node.js & Express/
Lesson

Imagine you're running a sushi restaurant. Each order starts at the counter, gets prepared by the chef, checked by the quality inspector, and finally plated by the server before reaching the customer. At each station, someone can modify the order, reject it, or pass it forward. Express middlewares work exactly like this assembly line.

What exactly is a middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.?

Simply put, a middleware is a function that sits between the incoming request and your final route handlerWhat is route handler?A Next.js file named route.js inside the app/ directory that handles HTTP requests directly - the App Router equivalent of API routes.. It has access to three things:

  • req: the request object (what the client sent)
  • res: the response object (what you'll send back)
  • next: a function to pass control to the next middleware

Here's what it looks like:

function myMiddleware(req, res, next) {
  // Do something with the request
  console.log('Request received:', req.method, req.url);
  
  // Pass to the next middleware in the chain
  next();
}

// Or with arrow functions (more common)
const logger = (req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
};
Good to know
If you forget to call next(), your request will hang indefinitely. The client will wait... and wait... until it times out. Express won't automatically move to the next middleware, you're in control.
02

Three types of middlewares

Not all middlewares are created equal. Express gives you three ways to use them, depending on how broadly you want to apply them.

Application-level middlewares

These apply to your entire Express app. They're the most common and get registered with app.use().

// This logger runs on EVERY request to your app
app.use(logger);

// This auth middleware only runs on routes starting with /api
app.use('/api', authMiddleware);

Think of application-level middlewares as your restaurant's house rules, they apply to everyone walking through the door, or to specific sections of the restaurant.

Router-level middlewares

Sometimes you want middlewares that only apply to a specific group of routes. That's where Express routers come in.

const userRouter = express.Router();

// This validation only applies to user routes
userRouter.use(validateUserData);

userRouter.get('/', getAllUsers);
userRouter.post('/', createUser);
userRouter.put('/:id', updateUser);

// Mount the router
app.use('/users', userRouter);

It's like having a special prep station just for vegetarian dishes, the validation rules only apply there, not to the entire kitchen.

Route-specific middlewares

The most granular option: middlewares attached to a single route.

// These middlewares only run for GET /admin
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
  res.json({ message: 'Welcome to the admin area' });
});

In our restaurant analogy, this is like requiring a manager's approval specifically for refunds, not for every transactionWhat is transaction?A group of database operations that either all succeed together or all fail together, preventing partial updates., just this specific action.

03

The execution order matters

Here's where things get interesting. Middlewares don't just run, they run in order. The sequence in which you declare them determines the sequence in which they execute.

// First middleware
app.use((req, res, next) => {
  console.log('1. Logger middleware');
  next();
});

// Second middleware (only for /admin)
app.use('/admin', (req, res, next) => {
  console.log('2. Auth middleware');
  next();
});

// Route handler
app.get('/admin', (req, res) => {
  console.log('3. Route handler');
  res.send('Admin page');
});

When you make a GET request to /admin, the console will show:

1. Logger middleware
2. Auth middleware
3. Route handler

But if you request /public, you'll only see:

1. Logger middleware

Why this matters
Order isn't just about timing, it affects data availability. If middleware B needs data set by middleware A, A must be declared first. If authentication middleware runs after your route handler, well... your routes aren't protected.
04

Stopping the chain

Middlewares don't have to pass control forward. They can stop the chain entirely by sending a response. This is how authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. works.

const requireAuth = (req, res, next) => {
  const token = req.headers.authorization;
  
  // No token? Stop right here.
  if (!token) {
    return res.status(401).json({ 
      error: 'Authentication required' 
    });
  }
  
  // Token exists? Continue to the next middleware
  next();
};

// Protected route
app.get('/dashboard', requireAuth, (req, res) => {
  res.json({ message: 'Secret dashboard data' });
});

Notice the return before res.status(401). Without it, your function would continue executing after sending the response, potentially causing errors. Always return when you send an error response in a middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it..

05

Modifying requests and responses

One of the most powerful features of middlewares is their ability to attach data to the request object. Since the same req object flows through the entire chain, data added by one middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. is available to all subsequent ones.

// Middleware that adds timing data
app.use((req, res, next) => {
  req.timestamp = Date.now();
  next();
});

// Middleware that adds user info (from a token)
app.use((req, res, next) => {
  const token = req.headers.authorization;
  if (token) {
    req.user = { id: '123', role: 'admin' };
  }
  next();
});

// Later in your routes, all this data is available
app.get('/data', (req, res) => {
  console.log('Request started at:', req.timestamp);
  console.log('User role:', req.user?.role);
  res.json({ timestamp: req.timestamp });
});

Think of req as a shared clipboard that travels down the assembly line. Each station can write on it, and every station downstream can read what's been written.

06

Quick reference: middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. flow

What happensCode patternResult
Continue to next middlewarenext()Request proceeds
End with successres.json() or res.send()Response sent to client
End with errorreturn res.status().json()Error response, chain stops
Modify requestreq.customProperty = valueData available downstream
Skip remaining middlewaresDon't call next() or resRequest hangs (bad!)
07

Common middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. patterns

Here's a cheat sheet of what you'll build most often:

// Logging middleware
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
};

// Timing middleware
const timer = (req, res, next) => {
  req.startTime = Date.now();
  res.on('finish', () => {
    console.log(`Request took ${Date.now() - req.startTime}ms`);
  });
  next();
};

// Simple auth check
const requireAuth = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
};

That's the essence of middlewares. They're simple functions with extraordinary power. Once you understand this pattern, you've unlocked the secret to building clean, modular Express applications.